{
  "openapi": "3.1.0",
  "info": {
    "title": "Tamarind Bio API",
    "version": "1.0",
    "description": "The complete Tamarind Bio public API.\n\n- **Jobs, tools, and files** — under `/api`. Submit and manage runs of any tool in the catalog.\n- **Pipelines and molecules** — under `/api/pipelines` and `/api/molecules`. Multi-step pipeline templates/runs, and the molecule store.\n\nEvery request authenticates with an `x-api-key` header."
  },
  "servers": [
    {
      "url": "https://structure-prediction-jwticvabh-tamarind-team.vercel.app",
      "description": "Tamarind API"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/api/submit-job": {
      "post": {
        "summary": "Submit a single job",
        "description": "Submit a job for protein analysis using one of the available tools",
        "operationId": "submitJob",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobSubmission"
              },
              "example": {
                "jobName": "my-protein-analysis",
                "type": "alphafold",
                "settings": {
                  "sequence": "MKTVRQERLKSIVRILERSKEPVSGAQLAEELSVSRQVIVQDIAYLRSLGYNIVATPRGYVLAGG"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job submitted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "tags": [
          "Jobs"
        ]
      }
    },
    "/api/submit-batch": {
      "post": {
        "summary": "Submit multiple jobs as a batch",
        "description": "Submit multiple jobs in a single request for batch processing",
        "operationId": "submitBatch",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchSubmission"
              },
              "example": {
                "batchName": "my-batch-analysis",
                "type": "alphafold",
                "settings": [
                  {
                    "sequence": "QVQLQQSGAELARPGASVKMSCKASGYTFTRYTMHWVKQRPGQGLEWIGYINPSRGYTNYNQKFKDKATLTTDKSSSTAYMQLSSLTSEDSAVYYCARYYDDHYCLDYWGQGTTLTVSS"
                  },
                  {
                    "sequence": "MKTVRQERLKSIVRILERSKEPVSGAQLAEELSVSRQVIVQDIAYLRSLGYNIVATPRGYVLAGG"
                  }
                ],
                "jobNames": [
                  "job1",
                  "job2"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch submitted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters"
          }
        },
        "tags": [
          "Jobs"
        ]
      }
    },
    "/api/jobs": {
      "get": {
        "summary": "Get user's jobs",
        "description": "Retrieve a list of jobs for the authenticated user",
        "operationId": "getJobs",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobName",
            "in": "query",
            "description": "Return one job given its name",
            "schema": {
              "type": "string",
              "example": "myJobName"
            }
          },
          {
            "name": "batch",
            "in": "query",
            "description": "Return all (sub)jobs in a batch. Note: these report Complete as soon as they finish computing, before the batch's aggregated output is ready. To know when the output is downloadable, fetch the batch's parent with ?jobName=<batchName> and poll its batchStatus field.",
            "schema": {
              "type": "string",
              "example": "myBatchName"
            }
          },
          {
            "name": "startKey",
            "in": "query",
            "description": "Use a previously returned start key to retrieve more than 1000 jobs",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Limit the number of jobs retrieved, if there are additional jobs a startKey will be returned",
            "schema": {
              "type": "integer",
              "default": 1000
            }
          },
          {
            "name": "organization",
            "in": "query",
            "description": "Return all jobs in your organization",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "includeSubjobs",
            "in": "query",
            "description": "Include subjobs from batches in response - only top level jobs are returned by default",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "jobEmail",
            "in": "query",
            "description": "Return jobs for another member in your organization",
            "schema": {
              "type": "string",
              "format": "email",
              "example": "user@email.com"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of jobs",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "jobs": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/JobInfo"
                          }
                        },
                        "startKey": {
                          "type": "string",
                          "description": "Key for pagination to retrieve more jobs"
                        },
                        "statuses": {
                          "type": "object",
                          "properties": {
                            "Complete": {
                              "type": "integer",
                              "description": "Number of completed jobs"
                            },
                            "In Queue": {
                              "type": "integer",
                              "description": "Number of jobs in queue"
                            },
                            "Running": {
                              "type": "integer",
                              "description": "Number of running jobs"
                            },
                            "Stopped": {
                              "type": "integer",
                              "description": "Number of stopped jobs"
                            }
                          }
                        }
                      }
                    },
                    {
                      "$ref": "#/components/schemas/JobInfo",
                      "description": "Single job response when jobName parameter is used"
                    }
                  ]
                }
              }
            }
          }
        },
        "tags": [
          "Jobs"
        ]
      }
    },
    "/api/result": {
      "post": {
        "summary": "Get job results",
        "description": "Retrieve results for a completed job",
        "operationId": "getResult",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jobName"
                ],
                "properties": {
                  "jobName": {
                    "type": "string",
                    "description": "Name of the job to get results for",
                    "example": "my-protein-analysis"
                  },
                  "jobEmail": {
                    "type": "string",
                    "format": "email",
                    "description": "Email of another member of your team (optional)",
                    "example": "user@email.com"
                  },
                  "fileName": {
                    "type": "string",
                    "description": "Path to a specific file in the job results (optional)",
                    "example": "myfile.txt"
                  },
                  "pdbsOnly": {
                    "type": "boolean",
                    "description": "Return only PDB files (optional)",
                    "example": true
                  },
                  "noAsync": {
                    "type": "boolean",
                    "description": "If true, fail with 400 instead of returning a 202 \"preparing\" response when the aggregated zip is not yet built. Use this if your client cannot poll. Default: false (the server falls back to an async build on miss).",
                    "example": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job results. Returned when the aggregated zip already exists, or when the server was able to build it inline (short-ETA batches: the request is held open for up to ~290s while the batch-aggregate worker finishes, then the signed URL is returned).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "description": "S3 presigned URL to download the job results",
                  "example": "https://s3.amazonaws.com/bucket/job-results.zip"
                }
              }
            }
          },
          "202": {
            "description": "The aggregated result zip is not yet built and its estimated build time exceeds the inline-wait budget (~290s), or the wait budget elapsed before the zip was ready. The server has kicked off (or rejoined) a batch-aggregate worker that will build it on demand and upload it to the same S3 key /result would have returned. Poll the batch parent (/jobs?jobName=<batchName>) until its `resultUrl` field appears, then re-call /result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "preparing"
                      ]
                    },
                    "jobName": {
                      "type": "string",
                      "description": "The batch parent's job name (echoed from the request)."
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable polling instructions."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          }
        },
        "tags": [
          "Results"
        ]
      }
    },
    "/api/upload/{filename}": {
      "put": {
        "summary": "Upload a file",
        "description": "Upload a file (PDB, sequence, etc.) for use in job submissions",
        "operationId": "uploadFile",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "filename",
            "in": "path",
            "required": true,
            "description": "Name of the file to upload",
            "schema": {
              "type": "string",
              "example": "myfile.pdb"
            }
          },
          {
            "name": "folder",
            "in": "query",
            "description": "Optional folder to upload the file to",
            "schema": {
              "type": "string",
              "example": "myFolder"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary",
                "description": "File content to upload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File uploaded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Success message",
                      "example": "File uploaded successfully"
                    },
                    "fileUrl": {
                      "type": "string",
                      "description": "URL of the uploaded file"
                    },
                    "signedUrl": {
                      "type": "string",
                      "description": "Signed URL for accessing the file"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid file"
          },
          "413": {
            "description": "File too large"
          }
        },
        "tags": [
          "Files"
        ]
      }
    },
    "/api/delete-job": {
      "delete": {
        "summary": "Delete a job",
        "description": "Marks a job deleted and hides it from listings; for a batch, its subjobs too. This is a soft delete — result files in storage are not removed. An unknown job name returns 400.",
        "operationId": "deleteJob",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jobName"
                ],
                "properties": {
                  "jobName": {
                    "type": "string",
                    "description": "Name of the job to delete",
                    "example": "myJobName"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "Job deleted successfully"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - job not found"
          }
        },
        "tags": [
          "Jobs"
        ]
      }
    },
    "/api/delete-file": {
      "delete": {
        "summary": "Delete a file",
        "description": "Delete a file from user account",
        "operationId": "deleteFile",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "filePath",
            "in": "query",
            "description": "Name of the file to delete",
            "schema": {
              "type": "string",
              "example": "path/to/myFileName.txt"
            }
          },
          {
            "name": "folder",
            "in": "query",
            "description": "Path to folder - deletes all files in the specified folder",
            "schema": {
              "type": "string",
              "example": "myFolder"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "File deleted successfully"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - file not found"
          }
        },
        "tags": [
          "Files"
        ]
      }
    },
    "/api/files": {
      "get": {
        "summary": "Get user's files",
        "description": "Retrieve a list of files uploaded by the user",
        "operationId": "getFiles",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of files to return",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of files to skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "includeFolders",
            "in": "query",
            "description": "Include folders in the response",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "folder",
            "in": "query",
            "description": "Path to folder to view files within that folder",
            "schema": {
              "type": "string",
              "example": "myFolder"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of files",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "files": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "fileId": {
                            "type": "string",
                            "description": "Unique identifier for the file"
                          },
                          "fileName": {
                            "type": "string",
                            "description": "Original filename"
                          },
                          "fileSize": {
                            "type": "integer",
                            "description": "File size in bytes"
                          },
                          "uploadTime": {
                            "type": "string",
                            "format": "date-time",
                            "description": "When the file was uploaded"
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total number of files"
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there are more files available"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Files"
        ]
      }
    },
    "/api/tools": {
      "get": {
        "summary": "List available tools",
        "description": "The tools this account can DISCOVER, each with its settings schema. Scoped to the caller. Fetch this rather than assuming a tool name.\n\nAbsence does not prove a tool is unsubmittable: custom tools deployed on the current platform are runnable, and their schemas are available at `/tools/{name}/schema`, but they are not listed here (see the `custom` parameter).",
        "operationId": "listTools",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "custom",
            "in": "query",
            "description": "Return your organization's custom tools instead of the built-in catalogue.\n\nLists tools from the legacy custom-tool store only. Custom tools deployed on the current platform are submittable, and their schemas are available at `/tools/{name}/schema`, but they are not returned here — if you deploy through the current platform, use the tool name you deployed rather than discovering it through this parameter.",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The available tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ToolInfo"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid API key"
          }
        },
        "tags": [
          "Tools"
        ]
      }
    },
    "/api/tools/{name}/schema": {
      "get": {
        "summary": "A tool's settings as JSON Schema",
        "description": "The parameters this tool accepts, as a standard JSON Schema document, scoped to your account. Validate a `settings` object against it before submitting. Domain types JSON Schema cannot express (a PDB file, a residue selection) travel as strings and keep their original type under `x-tamarind-type`.\n\nResolved through the same classifier `POST /submit-job` uses, so the schema describes the tool version your submission will actually run. A tool you cannot see and one that does not exist both answer 404.\n\nA custom tool that is mid-deploy (status `In Queue` or `Running`) also answers 404, because `/submit-job` refuses it in that state — the schema is unavailable until its deployment finishes rather than describing a contract you cannot submit.\n\nThis is a STATIC description, for code generation, form building and offline checking. To check a specific payload's FIELDS use `POST /validate-job`, which runs the same validator `/submit-job` does and so cannot disagree with it about field values. Note it validates fields only: it does not re-check org or team tool policy, or whether a custom tool is mid-deploy, so a job can still be refused at submission for those reasons.",
        "operationId": "getToolSchema",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "toolRef",
            "in": "query",
            "description": "Describe a specific pinned build of a custom tool rather than the deployed one — the same `toolRef` accepted by `POST /submit-job`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The tool's `name`, as returned by `GET /tools`.",
            "schema": {
              "type": "string",
              "example": "alphafold"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON Schema for the tool's settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid API key. The classic surface answers 400 here rather than 401, and this endpoint follows it."
          },
          "404": {
            "description": "No such tool, or not available to this account — a tool you cannot run is reported the same way as one that does not exist."
          }
        },
        "tags": [
          "Tools"
        ]
      }
    },
    "/api/validate-job": {
      "post": {
        "summary": "Validate a job without submitting it",
        "description": "Runs the exact validation `/submit-job` runs, without submitting and at no cost. Returns 200 whether or not the payload is valid — read the `valid` field. On success `normalized` is the payload to submit, with defaults filled in.",
        "operationId": "validateJob",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "settings"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "example": "esmfold"
                  },
                  "settings": {
                    "$ref": "#/components/schemas/JobSubmission/properties/settings"
                  },
                  "jobName": {
                    "type": "string",
                    "description": "Optional — when given, a duplicate name is reported as invalid."
                  },
                  "toolRef": {
                    "type": "string",
                    "description": "Optional. Pin validation to a specific custom-tool build (the same `toolRef` you passed to `/tools/{name}/schema`). Omit it and validation resolves the deployed version, which may declare a different set of settings than the build you fetched the schema for."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verdict. Note that an invalid payload is also a 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid API key"
          },
          "405": {
            "description": "Method not allowed"
          }
        },
        "tags": [
          "Jobs"
        ]
      }
    },
    "/api/stop-job": {
      "post": {
        "summary": "Stop a running or queued job",
        "description": "Stops a job that is Running, In Queue, Pending or Waiting. For a batch, stops every stoppable child and the parent.",
        "operationId": "stopJob",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jobName"
                ],
                "properties": {
                  "jobName": {
                    "type": "string",
                    "example": "my-protein-analysis"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stopped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "stoppedCount": {
                      "type": "integer",
                      "description": "How many jobs were stopped, including batch children."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid API key, unknown job, or the job is not in a stoppable state"
          },
          "405": {
            "description": "Method not allowed"
          }
        },
        "tags": [
          "Jobs"
        ]
      }
    },
    "/api/finetuned-models": {
      "get": {
        "summary": "List your finetuned models",
        "description": "Models you own, plus those shared within your organization.",
        "operationId": "listFinetunedModels",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "Filter by finetune type, e.g. plm-finetune.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The available models",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "personalModels": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinetunedModel"
                      }
                    },
                    "organizationModels": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinetunedModel"
                      }
                    },
                    "totalCount": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid type or limit"
          },
          "401": {
            "description": "Missing or invalid credentials"
          }
        },
        "tags": [
          "Models"
        ]
      }
    },
    "/api/usage-statistics": {
      "get": {
        "summary": "Usage statistics",
        "description": "Weighted-hours, hours, or job counts. Organization scope is the default and covers every member; if the caller is not authorized for it, the request is served at user scope instead — read `metadata.scope` to see which was applied.",
        "operationId": "getUsageStatistics",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "statistic",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "hours",
                "weighted_hours",
                "jobs"
              ],
              "default": "hours"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "organization"
              ],
              "default": "organization"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage, one entry per member in scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string"
                          },
                          "total": {
                            "type": "number"
                          },
                          "tools": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "number"
                            }
                          }
                        }
                      }
                    },
                    "lastUpdated": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "metadata": {
                      "type": "object",
                      "properties": {
                        "statistic": {
                          "type": "string"
                        },
                        "scope": {
                          "type": "string",
                          "description": "The scope actually applied, which may be narrower than requested."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials"
          },
          "403": {
            "description": "Organization membership could not be verified"
          }
        },
        "tags": [
          "Usage"
        ]
      }
    },
    "/api/submit-pipeline": {
      "post": {
        "summary": "Create and run a new pipeline",
        "description": "Defines a multi-stage pipeline inline and submits it. Each stage names a task and the tools to run for it; a stage's outputs feed the next. This is the legacy pipeline API — new integrations should use the pipelines endpoints under `/api/pipelines`, which separate a reusable template from a run.",
        "operationId": "submitPipeline",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jobName",
                  "stages"
                ],
                "properties": {
                  "jobName": {
                    "type": "string",
                    "description": "Name for this pipeline, unique within your account."
                  },
                  "stages": {
                    "type": "array",
                    "minItems": 1,
                    "description": "The stages to run, in order.",
                    "items": {
                      "$ref": "#/components/schemas/PipelineStage"
                    }
                  },
                  "initialInputs": {
                    "type": "array",
                    "minItems": 1,
                    "description": "Inputs fed into the first stage. Required (non-empty) whenever any first-stage setting has the value `\"pipe\"`, which marks the field that each initial input is substituted into; omitting it then is a 400 `Missing initial inputs`. Each entry is a raw sequence, or the name of a file you uploaded — `.pdb`/`.sdf` are passed through as file inputs, and a `.fa`/`.fasta` is expanded server-side into its sequences.",
                    "items": {
                      "type": "string"
                    }
                  },
                  "projectTag": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submitted. Body is the plain-text confirmation `Pipeline {jobName} submitted to queue.`",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid API key, a missing/empty `stages`, a duplicate `jobName`, a stage with no task or tools, an unknown filter metric, or an unsupported tool."
          },
          "403": {
            "description": "A tool in the pipeline is not available to your account"
          },
          "503": {
            "description": "A tool could not be resolved (undeployed, or a transient error) — retry"
          }
        },
        "tags": [
          "Pipelines"
        ]
      }
    },
    "/api/models": {
      "get": {
        "summary": "List your deployed models",
        "description": "Custom models you have deployed, plus those shared within your organization. Deleted models are omitted.",
        "operationId": "listModels",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Return just this model instead of the full list.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deployed models — or, when `name` is given, that single model object.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "Model list",
                      "type": "object",
                      "properties": {
                        "personalModels": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DeployedModel"
                          }
                        },
                        "organizationModels": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DeployedModel"
                          }
                        },
                        "allModels": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DeployedModel"
                          }
                        },
                        "totalCount": {
                          "type": "integer"
                        }
                      }
                    },
                    {
                      "title": "Single model",
                      "description": "Returned when the `name` query parameter is supplied.",
                      "$ref": "#/components/schemas/DeployedModel"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "No model with that name"
          }
        },
        "tags": [
          "Models"
        ]
      }
    },
    "/api/deploy-model": {
      "post": {
        "summary": "Deploy a custom model",
        "description": "Deploys your own code as a tool on Tamarind. Upload the entrypoint script and any environment file first with `PUT /upload/{filename}`, then reference them by filename here. When no `environment` is given the environment is inferred, which is only supported for a `.py` entrypoint.",
        "operationId": "deployModel",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "entrypoint"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Unique model name; may not collide with a built-in tool."
                  },
                  "entrypoint": {
                    "type": "string",
                    "description": "An uploaded script path, or a command. `default` uses the image's own entrypoint."
                  },
                  "environment": {
                    "type": "string",
                    "description": "An uploaded environment file (conda, requirements, Dockerfile). Required unless the entrypoint is a `.py` script."
                  },
                  "fields": {
                    "description": "The settings your model takes, in the same shape as a tool's settings. Accepts the array or its JSON-encoded string form — deploy-model.js does `typeof fields === 'string' ? JSON.parse(fields) : fields`, so existing callers send the encoded string.",
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "object"
                        }
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "description": {
                    "type": "string"
                  },
                  "tags": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      {
                        "type": "string",
                        "description": "JSON-encoded array, accepted for backward compatibility."
                      }
                    ]
                  },
                  "gpu": {
                    "type": "boolean"
                  },
                  "outputs": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string"
                                },
                                "description": {
                                  "type": "string"
                                }
                              }
                            }
                          ]
                        }
                      },
                      {
                        "type": "string",
                        "description": "JSON-encoded array, accepted for backward compatibility."
                      }
                    ]
                  },
                  "outputType": {
                    "type": "string"
                  },
                  "outputDescription": {
                    "type": "string"
                  },
                  "runCommand": {
                    "type": "string"
                  },
                  "dockerImageType": {
                    "type": "string"
                  },
                  "dockerContext": {
                    "type": "string"
                  },
                  "contextZip": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deployed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployedModel"
                }
              }
            }
          },
          "400": {
            "description": "Missing `name`/`entrypoint`, a name that already exists, a referenced file that was never uploaded, or a missing environment for a non-Python entrypoint."
          },
          "405": {
            "description": "Method not allowed"
          }
        },
        "tags": [
          "Models"
        ]
      }
    },
    "/api/run-pipeline": {
      "post": {
        "summary": "Run a saved pipeline",
        "description": "Runs a saved multi-stage pipeline by name. This is the legacy pipeline API; new integrations should use the pipelines endpoints under `/api/pipelines`.",
        "operationId": "runPipeline",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jobName",
                  "pipelineName",
                  "initialInputs"
                ],
                "properties": {
                  "jobName": {
                    "type": "string",
                    "description": "Name for this pipeline execution."
                  },
                  "pipelineName": {
                    "type": "string"
                  },
                  "version": {
                    "type": "string",
                    "description": "Optional saved version; defaults to the pipeline's default."
                  },
                  "initialInputs": {
                    "type": "array",
                    "minItems": 1,
                    "description": "Uploaded .pdb filenames or raw sequences, matching the pipeline's configured input type. Must be non-empty. Basenames must be unique — child job names are derived from them.",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submitted",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Pipeline \"my-pipeline\" execution \"run-01\" submitted to queue with 3 jobs."
              }
            }
          },
          "400": {
            "description": "Missing or invalid API key, or invalid inputs"
          },
          "403": {
            "description": "Denied — a tool in the pipeline is restricted for this account, or a budget cap would be exceeded."
          },
          "404": {
            "description": "Pipeline not found"
          }
        },
        "tags": [
          "Pipelines"
        ]
      }
    },
    "/api/molecules/groups": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "List Groups",
        "description": "List your molecule groups — a group is a named collection of molecules.\n\n- Use `scope=org` to see every group in your organization, not just yours\n- Sort by `recent`, `name`, or `size`\n- Paginated: follow `nextCursor` until it is null",
        "operationId": "listGroups",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Case-insensitive substring match on the group's name.",
              "title": "Search"
            },
            "description": "Case-insensitive substring match on the group's name."
          },
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "maxItems": 32
                },
                {
                  "type": "null"
                }
              ],
              "title": "Filter"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(me|org)$",
              "description": "`me` (the default) shows groups you created; `org` shows every group in your organization.",
              "default": "me",
              "title": "Scope"
            },
            "description": "`me` (the default) shows groups you created; `org` shows every group in your organization."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(recent|name|size)$",
              "description": "Sort by `recent` (the default, by creation date), `name`, or `size` (how many molecules the group holds). Pair with `dir`.",
              "default": "recent",
              "title": "Sort"
            },
            "description": "Sort by `recent` (the default, by creation date), `name`, or `size` (how many molecules the group holds). Pair with `dir`."
          },
          {
            "name": "dir",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(asc|desc)$",
              "description": "Sort direction, `asc` or `desc`.",
              "default": "desc",
              "title": "Dir"
            },
            "description": "Sort direction, `asc` or `desc`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "description": "Maximum number of items to return in one page.",
              "default": 50,
              "title": "Limit"
            },
            "description": "Maximum number of items to return in one page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicGroupPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Create Group",
        "description": "Create an empty group, then add molecules to it.\n\n- Load it with `POST /molecules/upload` (JSON) or `POST /molecules/import-file` (a file)\n- Optionally bind a `schemaId` to require every molecule to match that schema",
        "operationId": "createGroup",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicCreateGroupRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicGroup"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/groups/{group_id}": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Get Group",
        "description": "Fetch one group by id — its name, size, and origin.",
        "operationId": "getGroup",
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Group Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicGroup"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/schemas": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "List Schemas",
        "description": "List your schemas.\n\n- Use `scope=org` to include every schema in your organization",
        "operationId": "listSchemas",
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(me|org)$",
              "default": "me",
              "title": "Scope"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "description": "Maximum number of items to return in one page.",
              "default": 50,
              "title": "Limit"
            },
            "description": "Maximum number of items to return in one page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSchemaPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Create Schema",
        "description": "Define a reusable set of typed fields you can bind to a group.\n\n- Scalar fields (`string`, `integer`, `float`, `boolean`, `category`) constrain a molecule's metadata\n- A `chain` field describes a required chain; its `name` is the chain id",
        "operationId": "createSchema",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicCreateSchemaRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSchema"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/schemas/{schema_id}": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Get Schema",
        "description": "Fetch one schema by id.",
        "operationId": "getSchema",
        "parameters": [
          {
            "name": "schema_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Schema Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSchema"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "patch": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Update Schema",
        "description": "Update a schema's name and/or fields.\n\n- Sending `fields` replaces the whole list\n- Only future uploads are validated; molecules already in bound groups are left as they are",
        "operationId": "updateSchema",
        "parameters": [
          {
            "name": "schema_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Schema Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicUpdateSchemaRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicSchema"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/remove": {
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Remove Molecules",
        "description": "Remove molecules from a group — pass `groupId` and `moleculeIds` in the body.\n\n- This detaches group membership; it does not delete the molecule\n- A molecule in other groups stays in them, with its scores and files intact\n- Idempotent: ids not in the group are ignored; `removedIds` lists what was detached\n- To delete a molecule everywhere, use `DELETE /molecules/{moleculeId}`",
        "operationId": "removeMolecules",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicRemoveMoleculesFromGroupRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRemoveMoleculesResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/upload": {
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Upload Molecules",
        "description": "Add molecules to a group directly as JSON — no file upload. Queued, not synchronous.\n\n- A molecule's identity comes from its chains, so uploading one that already exists returns the existing molecule and adds it to the group. No duplicates are created, and the call is safe to retry.\n- Returns `202` with `status: \"pending\"` and an `importId`; the molecules are queued and not queryable yet — poll `GET /molecules/imports/{importId}` until it reports `ingested`. `moleculeIds` is exact and available immediately (content-addressed from what you sent), so record it straight away rather than re-uploading on the `202`.\n- Pass `?wait=true` to block until ingestion finishes and get `200` with `status: \"ingested\"` (falling back to `202` if the worker misses the budget). Fine for a one-off script; avoid it under concurrency, where a blocked request pins a thread and DB connection against the gateway's 30s ceiling.",
        "operationId": "uploadMolecules",
        "parameters": [
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false,
              "title": "Wait"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicUploadRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicUploadResponse"
                }
              }
            }
          },
          "202": {
            "description": "Queued; poll the import",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicUploadResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/import-file": {
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Start a file import",
        "description": "Step 1 of 3: import molecules from a file. Declare the file and get a presigned `uploadUrl`.\n\n- Then `PUT` the file to `uploadUrl`, and `POST /molecules/imports/{importId}/commit` to ingest it\n- For a CSV, map chain columns with `chainMapping` and scalar columns with `columnMapping`\n- For PDB, SDF, FASTA, or zip files, the chains are read from the file",
        "operationId": "importFile",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicFileImportRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicFileImportStart"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/imports/{import_id}": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Check import status",
        "description": "Check an import's progress after committing with `wait=false`.\n\n- Status moves `created` → `uploaded` → `queued` → `ingested` (or `failed`)\n- Reflects this import specifically, not the target group's overall state",
        "operationId": "getImport",
        "parameters": [
          {
            "name": "import_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Import Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicImportStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/imports/{import_id}/commit": {
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Commit an import",
        "description": "Step 3 of 3: parse the uploaded file and ingest its molecules into the group.\n\n- Returns `status: \"queued\"` immediately by default — poll `GET /molecules/imports/{importId}`.\n- Pass `?wait=true` to block until the molecules are queryable and get the finished group back. It can still return `status: \"queued\"` with HTTP `202` if the worker misses the wait budget — that's not a failure; fall through to the same poll loop rather than re-committing.",
        "operationId": "commitImport",
        "parameters": [
          {
            "name": "import_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Import Id"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false,
              "title": "Wait"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/PublicCommitRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Body"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/PublicCommitQueued"
                    },
                    {
                      "$ref": "#/components/schemas/PublicCommitFinished"
                    }
                  ],
                  "title": "Response Commitimport"
                }
              }
            }
          },
          "202": {
            "description": "Dispatched; still ingesting — poll the import",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicCommitQueued"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "List Molecules",
        "description": "List your molecules, most recently created first.\n\n- Use `scope=org` to list across your whole organization, not just your account\n- Filter by `group`, `jobId`/`jobName`, `type`, `search`, `sequence`, or `filter` (a metadata field or tool score)\n- Sort by a built-in column or a tool score with `sortBy` (e.g. `alphafold.ptm`)\n- Each molecule includes its chains, scores, metadata, and files inline\n- Chain labels are per-group; pass a `group` to get a structure file's download link\n- Paginated: follow `nextCursor` until it is null",
        "operationId": "listMolecules",
        "parameters": [
          {
            "name": "group",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit to one group (by id). A group that isn't yours returns an empty page.",
              "title": "Group"
            },
            "description": "Limit to one group (by id). A group that isn't yours returns an empty page."
          },
          {
            "name": "jobId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit to one job's molecules — both those it produced and those it consumed. Pass a batch id, not a child job id; an unknown id returns an empty page.",
              "title": "Jobid"
            },
            "description": "Limit to one job's molecules — both those it produced and those it consumed. Pass a batch id, not a child job id; an unknown id returns an empty page."
          },
          {
            "name": "jobName",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "The same, matching by job or batch name. Names aren't unique, so several jobs may match; all the matches you can see are included.",
              "title": "Jobname"
            },
            "description": "The same, matching by job or batch name. Names aren't unique, so several jobs may match; all the matches you can see are included."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MoleculeType"
                  }
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit by molecule kind (e.g. protein, small_molecule, nucleic_acid).",
              "title": "Type"
            },
            "description": "Limit by molecule kind (e.g. protein, small_molecule, nucleic_acid)."
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Match on molecule name.",
              "title": "Search"
            },
            "description": "Match on molecule name."
          },
          {
            "name": "sequence",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Match molecules containing this amino-acid subsequence in any chain (case-insensitive, at least 3 characters).",
              "title": "Sequence"
            },
            "description": "Match molecules containing this amino-acid subsequence in any chain (case-insensitive, at least 3 characters)."
          },
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "maxItems": 32
                },
                {
                  "type": "null"
                }
              ],
              "description": "Repeatable `field:operator:value` predicate (AND-ed) on any metadata field, including a tool score as `tool.metric`. Append `:jobId` to pin a tool to one run.",
              "title": "Filter"
            },
            "description": "Repeatable `field:operator:value` predicate (AND-ed) on any metadata field, including a tool score as `tool.metric`. Append `:jobId` to pin a tool to one run."
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(me|org)$",
              "description": "`me` (the default) shows molecules in groups you created; `org` shows everything across your organization.",
              "default": "me",
              "title": "Scope"
            },
            "description": "`me` (the default) shows molecules in groups you created; `org` shows everything across your organization."
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Sort by a built-in column (`id`, `added`, `type`) or a metadata field, including a tool score as `tool.metric` (ranked on the tool's best run).",
              "default": "added",
              "title": "Sortby"
            },
            "description": "Sort by a built-in column (`id`, `added`, `type`) or a metadata field, including a tool score as `tool.metric` (ranked on the tool's best run)."
          },
          {
            "name": "dir",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(asc|desc)$",
              "description": "Sort direction, `asc` or `desc`.",
              "default": "desc",
              "title": "Dir"
            },
            "description": "Sort direction, `asc` or `desc`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "description": "Maximum number of items to return in one page.",
              "default": 50,
              "title": "Limit"
            },
            "description": "Maximum number of items to return in one page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicMoleculePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/{molecule_id}/metadata": {
      "patch": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Update Molecule Metadata",
        "description": "Update a molecule. All fields are optional and applied together; `null` clears a field.\n\n- `properties` — merge your own annotations into the molecule's metadata (a `null` value removes a key; tool scores aren't editable here)\n- `source` — the parent molecule this one was derived from\n- `fileId` — the structure file to use as this group's primary\n- `name` — the molecule's per-group display name (a name already used in the group returns 409)",
        "operationId": "updateMoleculeMetadata",
        "parameters": [
          {
            "name": "molecule_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Molecule Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicUpdateMetadataRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicMolecule"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/{molecule_id}/groups": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "List Molecule Groups",
        "description": "List every group a molecule belongs to, paginated.\n\n- Use this when `GET /molecules/{moleculeId}` marks `\"groups\"` as truncated\n- Only your own groups are listed\n- Follow `nextCursor` until it is null",
        "operationId": "listMoleculeGroups",
        "parameters": [
          {
            "name": "molecule_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Molecule Id"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "description": "Maximum number of items to return in one page.",
              "default": 50,
              "title": "Limit"
            },
            "description": "Maximum number of items to return in one page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit it for the first page, then follow `nextCursor` until it is null — an empty `items` page is normal while `nextCursor` is still set."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicGroupPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/{molecule_id}": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Get Molecule",
        "description": "Get one molecule — its chains, scores, files, provenance, and groups, all inline.\n\n- Addressed by its single id; no group needed\n- Includes `entity`/`chainMapping` (chains), `metadata` (scores and annotations), `files`, `origin`, `source`, and `groups`\n- Chain labels are per-group: pass `groupId` to choose which group's labels you get, else the most recent group wins",
        "operationId": "getMolecule",
        "parameters": [
          {
            "name": "molecule_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Molecule Id"
            }
          },
          {
            "name": "groupId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "title": "Groupid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicMolecule"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Delete Molecule",
        "description": "Permanently delete a molecule. Admin only — an organization admin or Tamarind staff.\n\n- Removes the molecule and all its group memberships, scores, and file links across the organization\n- To remove it from one group instead, use `POST /molecules/remove`\n- Idempotent: an unknown id returns `deleted: false`",
        "operationId": "deleteMolecule",
        "parameters": [
          {
            "name": "molecule_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Molecule Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDeleteMoleculeResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/pipelines/templates": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Create a pipeline template",
        "description": "Create a pipeline template from a `pipeline` graph of inputs, tools, and filters.\n\n- The graph is validated on create\n- This first save becomes the template's first version; every later save adds a new immutable version",
        "operationId": "createTemplate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicCreateTemplateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTemplate"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      },
      "get": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "List pipeline templates",
        "description": "List pipeline templates you can see.\n\n- Filter by `isPublished`, `owner` (`mine`/`org`), or a name `search`",
        "operationId": "listTemplates",
        "parameters": [
          {
            "name": "isPublished",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Ispublished"
            }
          },
          {
            "name": "owner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "mine",
                    "org"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Owner"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "title": "Search"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "title": "Cursor"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTemplatePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/templates/{template_id}": {
      "get": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Get a pipeline template",
        "description": "Get a template, including its run-default version's `pipeline` graph and the `inputs` a run must bind.\n\n- A run without a `version` uses the published version if one is pinned, otherwise the latest saved\n- This describes that same run-default version",
        "operationId": "getTemplate",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTemplate"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      },
      "delete": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Delete a pipeline template",
        "description": "Delete a template and all its versions.\n\n- Existing runs keep their own copies and are unaffected",
        "operationId": "deleteTemplate",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/templates/{template_id}/versions": {
      "get": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "List a template's versions",
        "description": "List a template's saved versions, newest first.\n\n- Each version is an immutable snapshot; a run executes a specific one",
        "operationId": "listTemplateVersions",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicVersionPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/templates/{template_id}/publish": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Publish a template version",
        "description": "Publish one version of a template to your organization.\n\n- The published version is the default others run\n- Only one version can be published at a time",
        "operationId": "publishTemplate",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicPublishRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPublishResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/templates/{template_id}/duplicate": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Duplicate a pipeline template",
        "description": "Fork a template into a new template owned by you; the source is unchanged.\n- Anyone who can view a template can duplicate it\n- Pass `version` to fork that exact version; omit it to fork the published (or latest saved) version\n- The copy starts fresh: no version history, runs, sharing, or published state\n- Not idempotent — `Idempotency-Key` isn't honored, so a retry creates a second copy",
        "operationId": "duplicateTemplate",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/PublicDuplicateTemplateRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Body"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTemplate"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/templates/{template_id}/runs": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Submit a pipeline run",
        "description": "Submit a run of a template — one execution against the molecules you bind to its inputs.\n\n- Provide a `bindings` map, one entry per input slot\n- Omit `version` to run the published version, or the latest saved if none is published\n- Pass an `Idempotency-Key` header to make a retry safe\n- Runs asynchronously — poll `GET /runs/{runId}`; outputs land back in the molecules database",
        "operationId": "submitRun",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 255
                },
                {
                  "type": "null"
                }
              ],
              "title": "Idempotency-Key"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicSubmitRunRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRun"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/templates/{template_id}/validate": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Validate a would-be run",
        "description": "Check a would-be run without starting it — same body and permissions as `submitRun`.\n- Always returns 200; the verdict is in the body (`valid` plus `diagnostics`)\n- Runs the full pre-submit check, including the engine's executable resolution\n- `reachedTier: runtime` with nothing `unchecked` means the run is runnable; `fit` means the engine was unavailable and the real submit could still fail\n- Checks runnability, not budget — an over-budget run validates `true` but `submitRun` returns 403",
        "operationId": "validatePipeline",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicSubmitRunRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicValidateResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/runs": {
      "get": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "List pipeline runs",
        "description": "List pipeline runs in your organization.\n\n- Filter by `status`, `templateId`, `owner` (`mine`/`org`), or `source` (`test`/`production`)",
        "operationId": "listRuns",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/RunStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Status"
            }
          },
          {
            "name": "templateId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Templateid"
            }
          },
          {
            "name": "owner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "mine",
                    "org"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Owner"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "test",
                    "production"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Source"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "title": "Cursor"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRunPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/runs/{run_id}": {
      "get": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Get a pipeline run",
        "description": "Get a run — its overall `status` and one step per node, each with its own status and outputs.\n\n- Poll this until `status` leaves `queued`/`running`",
        "operationId": "getRun",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Run Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRun"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/runs/{run_id}/stop": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Stop a pipeline run",
        "description": "Stop an in-flight run.\n- No further steps are submitted, and jobs already queued or running are stopped\n- Idempotent: the returned `status` is the run's real state (stopping a finished run reports `finished`)\n- Outputs already produced are kept; a stopped run cannot be resumed — submit it again",
        "operationId": "stopRun",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Run Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRun"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your Tamarind API key. Create one at https://app.tamarind.bio/api-docs/api-key."
      }
    },
    "schemas": {
      "DiagnosticCode": {
        "type": "string",
        "enum": [
          "parse-error",
          "shape-violation",
          "structural-limit",
          "cycle",
          "dangling-ref",
          "unsupported-schema-version",
          "tool-unknown",
          "setting-invalid",
          "param-out-of-range",
          "required-field-unset",
          "input-unbound",
          "input-missing-reference",
          "chain-incompatible",
          "chain-unsatisfied",
          "molecule-class-incompatible",
          "binding-invalid",
          "budget-exceeded",
          "tool-not-licensed",
          "runtime-unresolved",
          "unknown"
        ],
        "title": "DiagnosticCode",
        "description": "The PUBLIC validation-diagnostic vocabulary — the stable value set a caller may switch on.\n\nThis is a CURATED contract, not a passthrough of the internal code set: the mapper\n(`_map/pipelines._diagnostic`) translates each internal code to one of these, and an internal\ncode with no public mapping becomes `unknown` (never a raw internal string). So a new INTERNAL\ndiagnostic code cannot silently enter the public contract — adding a public code is a deliberate,\nv1-frozen change. Keep in sync with the mapper's translation table."
      },
      "Flow": {
        "type": "string",
        "enum": [
          "molecule",
          "file"
        ],
        "title": "Flow"
      },
      "MoleculeChainInfo": {
        "properties": {
          "type": {
            "type": "string",
            "title": "Type"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Tags"
          }
        },
        "type": "object",
        "required": [
          "type",
          "tags"
        ],
        "title": "MoleculeChainInfo",
        "description": "Type + role tags of one chain in a molecule's `entity`, keyed by the same\nchain id — so a reader tells a protein sequence from a SMILES, and sees\nheavy/light/lead roles, without loading the group's schema."
      },
      "MoleculeClass": {
        "type": "string",
        "enum": [
          "protein",
          "small_molecule",
          "nucleic_acid"
        ],
        "title": "MoleculeClass"
      },
      "MoleculeFileEntry": {
        "properties": {
          "fileName": {
            "type": "string",
            "title": "Filename"
          },
          "fileType": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Filetype"
          },
          "downloadUrl": {
            "type": "string",
            "title": "Downloadurl"
          },
          "createdAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Createdat"
          }
        },
        "type": "object",
        "required": [
          "fileName",
          "fileType",
          "downloadUrl",
          "createdAt"
        ],
        "title": "MoleculeFileEntry",
        "description": "One structure file on a molecule. Appears in the `files` map, whose KEY is\nthe producer (a job/tool name, or `user` for uploads)."
      },
      "MoleculeType": {
        "type": "string",
        "enum": [
          "protein",
          "antibody",
          "peptide",
          "enzyme",
          "small_molecule",
          "nucleic_acid",
          "small_molecule_binding_protein"
        ],
        "title": "MoleculeType",
        "description": "The spec's `MoleculeType` — the kind of a molecule, and of the molecules a\ngroup holds.\n\nValue-identical to the internal `Modality` (the user-picked upload modality),\nwhich is the superset enum `complexes.type` is written from. Declared\nseparately because it is a PUBLISHED contract: `Modality` is free to grow a\nvalue for an internal picker without that value silently becoming part of the\npublic API. `public_types_match_modality` pins them equal today."
      },
      "PipelineIR": {
        "properties": {},
        "additionalProperties": true,
        "type": "object",
        "title": "PipelineIR",
        "description": "A pipeline IR document. Full schema (typed, versioned): https://tamarind.bio/schemas/pipeline-v1.json"
      },
      "PublicBinding": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/PublicMoleculeBinding"
          },
          {
            "$ref": "#/components/schemas/PublicFileBinding"
          }
        ],
        "title": "PublicBinding",
        "discriminator": {
          "propertyName": "flow",
          "mapping": {
            "file": "#/components/schemas/PublicFileBinding",
            "molecule": "#/components/schemas/PublicMoleculeBinding"
          }
        }
      },
      "PublicChainMappingEntry": {
        "properties": {
          "type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicChainType"
              },
              {
                "type": "null"
              }
            ]
          },
          "tags": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/PublicChainTag"
                },
                "type": "array",
                "maxItems": 3
              },
              {
                "type": "null"
              }
            ],
            "title": "Tags",
            "description": "Roles for this chain. A chain is `heavy` OR `light`, never both (storage keeps one subtype); `lead` is compatible with either. Omit to inherit the schema's tag."
          },
          "csvColumn": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Csvcolumn"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicChainMappingEntry",
        "description": "The spec's `ChainMappingEntry` — how ONE chain is defined for ingestion,\nkeyed by chain id.\n\nThis is what REPLACED the old fixed CSV role vocabulary\n(`heavy_chain`/`light_chain`/`sequence`/...), which could only describe\nantibody-shaped data and could not name a chain id at all (design notes §1)."
      },
      "PublicChainTag": {
        "type": "string",
        "enum": [
          "heavy",
          "light",
          "lead"
        ],
        "title": "PublicChainTag",
        "description": "The spec's `ChainTag` — the functional role of one chain."
      },
      "PublicChainType": {
        "type": "string",
        "enum": [
          "protein",
          "small_molecule"
        ],
        "title": "PublicChainType",
        "description": "The spec's `ChainType` — the molecular kind of one chain."
      },
      "PublicCommitFinished": {
        "properties": {
          "importId": {
            "type": "string",
            "title": "Importid"
          },
          "status": {
            "type": "string",
            "const": "ingested",
            "title": "Status",
            "default": "ingested"
          },
          "groupId": {
            "type": "string",
            "title": "Groupid"
          },
          "groupName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Groupname"
          },
          "moleculeCount": {
            "type": "integer",
            "title": "Moleculecount"
          }
        },
        "type": "object",
        "required": [
          "importId",
          "groupId",
          "groupName",
          "moleculeCount"
        ],
        "title": "PublicCommitFinished",
        "description": "Returned when `wait=true` and ingestion completed."
      },
      "PublicCommitQueued": {
        "properties": {
          "importId": {
            "type": "string",
            "title": "Importid"
          },
          "status": {
            "type": "string",
            "const": "queued",
            "title": "Status",
            "default": "queued"
          },
          "groupName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Groupname"
          }
        },
        "type": "object",
        "required": [
          "importId",
          "groupName"
        ],
        "title": "PublicCommitQueued",
        "description": "Returned when `wait=false` (the default)."
      },
      "PublicCommitRequest": {
        "properties": {
          "columnMapping": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Columnmapping"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicCommitRequest",
        "description": "The spec's `CommitRequest` — optional overrides applied at commit time."
      },
      "PublicCreateGroupRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/MoleculeType"
              },
              {
                "type": "null"
              }
            ]
          },
          "schemaId": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Schemaid"
          },
          "orgId": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Orgid"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "maxItems": 64,
            "title": "Tags"
          },
          "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "name"
        ],
        "title": "PublicCreateGroupRequest",
        "description": "The spec's `CreateGroupRequest`.\n\nInline group creation was dropped from upload/import, so this is now the ONLY\nway a group comes into existence on the public surface."
      },
      "PublicCreateSchemaRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "fields": {
            "items": {
              "$ref": "#/components/schemas/PublicSchemaField"
            },
            "type": "array",
            "maxItems": 200,
            "minItems": 1,
            "title": "Fields"
          },
          "orgId": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Orgid"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "name",
          "fields"
        ],
        "title": "PublicCreateSchemaRequest"
      },
      "PublicCreateTemplateRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "pipeline": {
            "$ref": "#/components/schemas/PipelineIR",
            "description": "The pipeline IR. Every molecule `user_input` node must name a reference group in `metadata.defaultGroup` (the molecules the template is authored against — a run binds its own group at submit); a molecule input without one is rejected 422 `input-missing-reference`. File inputs are exempt."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "name",
          "pipeline"
        ],
        "title": "PublicCreateTemplateRequest",
        "example": {
          "description": "AF2 + ProteinMPNN",
          "name": "binder-design",
          "pipeline": {
            "nodes": {
              "af2": {
                "inputs": {
                  "sequence": [
                    {
                      "node": "target"
                    }
                  ]
                },
                "kind": "tool",
                "tool": "tamarind://alphafold"
              },
              "target": {
                "flow": "molecule",
                "kind": "user_input",
                "metadata": {
                  "defaultGroup": "3f8a1c2e-5b7d-4e9f-a1b2-c3d4e5f6a7b8"
                },
                "molecule_type": "protein"
              }
            },
            "schema_version": "1.0"
          }
        }
      },
      "PublicDeleteMoleculeResponse": {
        "properties": {
          "moleculeId": {
            "type": "string",
            "title": "Moleculeid"
          },
          "deleted": {
            "type": "boolean",
            "title": "Deleted"
          }
        },
        "type": "object",
        "required": [
          "moleculeId",
          "deleted"
        ],
        "title": "PublicDeleteMoleculeResponse"
      },
      "PublicDiagnostic": {
        "properties": {
          "code": {
            "$ref": "#/components/schemas/DiagnosticCode"
          },
          "severity": {
            "$ref": "#/components/schemas/Severity"
          },
          "node": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Node"
          }
        },
        "type": "object",
        "required": [
          "code",
          "severity",
          "node"
        ],
        "title": "PublicDiagnostic"
      },
      "PublicDuplicateTemplateRequest": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "version": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Version"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicDuplicateTemplateRequest",
        "description": "Body for `POST /templates/{templateId}/duplicate`.\n\nBoth fields are optional; `{}` is a valid body.\n\nVERSIONS ONLY — never the working draft. A draft is mutable and unversioned, so there is no\nstable thing for an API client to reference (\"the draft as of now\" isn't expressible) and it\nmay be a teammate's mid-edit graph. So `version` omitted resolves to the published version if\none is pinned, else the latest SAVED version. This is the one place the API deliberately\ndiffers from the in-app menu, which forks the draft because the user can see it.",
        "example": {
          "name": "binder-design v2 experiment",
          "version": "3f2a1c9e-8b7d-4e6f-a1b2-c3d4e5f60718"
        }
      },
      "PublicFastaMode": {
        "type": "string",
        "enum": [
          "one-entity-per-file",
          "one-entity-per-header"
        ],
        "title": "PublicFastaMode",
        "description": "The spec's `fastaMode` — how a FASTA file is split into molecules.\n\nDeliberately NOT the internal `FastaMode`'s spelling: the public contract says\n`one-entity-per-file` / `one-entity-per-header` where the internal enum says\n`one-complex-per-file` / `one-complex-per-line`. The public surface drops the\n\"complex\" vocabulary entirely, and `header` describes the FASTA `>` record more\nhonestly than `line`. `to_internal_fasta_mode` is the only bridge."
      },
      "PublicFieldType": {
        "type": "string",
        "enum": [
          "string",
          "integer",
          "float",
          "boolean",
          "category",
          "chain"
        ],
        "title": "PublicFieldType",
        "description": "The spec's `FieldType`.\n\nNOTE `chain` is a MEMBER of this enum, not a separate axis: a schema's `fields`\nlist interleaves scalar fields and chain fields, and `type == \"chain\"` is what\ndistinguishes them."
      },
      "PublicFileBinding": {
        "properties": {
          "flow": {
            "type": "string",
            "const": "file",
            "title": "Flow"
          },
          "file": {
            "type": "string",
            "title": "File"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "flow",
          "file"
        ],
        "title": "PublicFileBinding"
      },
      "PublicFileFormat": {
        "type": "string",
        "enum": [
          "auto",
          "csv",
          "fasta",
          "sdf",
          "pdb",
          "zip",
          "sdf_zip"
        ],
        "title": "PublicFileFormat",
        "description": "The spec's `FileFormat` — a STRICT SUBSET of the internal\n`MoleculeFileFormat`, which also carries `cif`/`mmcif`. The public API does not\ndocument those, so they aren't accepted here; `auto` still detects anything the\nworker can read."
      },
      "PublicFileImportRequest": {
        "properties": {
          "groupId": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Groupid"
          },
          "fileName": {
            "type": "string",
            "maxLength": 512,
            "minLength": 1,
            "title": "Filename"
          },
          "fileFormat": {
            "$ref": "#/components/schemas/PublicFileFormat",
            "default": "auto"
          },
          "sizeBytes": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 2147483648,
                "minimum": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Sizebytes"
          },
          "columnMapping": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Columnmapping"
          },
          "chainMapping": {
            "anyOf": [
              {
                "additionalProperties": {
                  "$ref": "#/components/schemas/PublicChainMappingEntry"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Chainmapping"
          },
          "fastaMode": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicFastaMode"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "groupId",
          "fileName"
        ],
        "title": "PublicFileImportRequest",
        "description": "The spec's `FileImportRequest`."
      },
      "PublicFileImportStart": {
        "properties": {
          "importId": {
            "type": "string",
            "title": "Importid"
          },
          "uploadUrl": {
            "type": "string",
            "title": "Uploadurl"
          },
          "uploadMethod": {
            "type": "string",
            "const": "PUT",
            "title": "Uploadmethod",
            "default": "PUT"
          },
          "uploadHeaders": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object",
            "title": "Uploadheaders"
          },
          "groupName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Groupname"
          },
          "expiresInSeconds": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expiresinseconds"
          }
        },
        "type": "object",
        "required": [
          "importId",
          "uploadUrl",
          "uploadHeaders",
          "groupName",
          "expiresInSeconds"
        ],
        "title": "PublicFileImportStart"
      },
      "PublicGroup": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "displayName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Displayname"
          },
          "matchedOn": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Matchedon"
          },
          "type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Type"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "moleculeCount": {
            "type": "integer",
            "title": "Moleculecount"
          },
          "schemaId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Schemaid"
          },
          "source": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicGroupSource"
              },
              {
                "type": "null"
              }
            ]
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Tags"
          },
          "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata"
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          },
          "updatedAt": {
            "type": "string",
            "title": "Updatedat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "displayName",
          "matchedOn",
          "type",
          "status",
          "moleculeCount",
          "schemaId",
          "source",
          "tags",
          "metadata",
          "createdAt",
          "updatedAt"
        ],
        "title": "PublicGroup",
        "description": "The spec's `Group` — a named collection of molecules.\n\nMolecule-only vocabulary: `moleculeCount`, never the internal `complexCount`."
      },
      "PublicGroupPage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicGroup"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "title": "PublicGroupPage"
      },
      "PublicGroupSource": {
        "properties": {
          "jobId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Jobid"
          },
          "toolName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Toolname"
          }
        },
        "type": "object",
        "required": [
          "jobId",
          "toolName"
        ],
        "title": "PublicGroupSource",
        "description": "`Group.source` — set when the group is a job's output."
      },
      "PublicImportStatus": {
        "properties": {
          "importId": {
            "type": "string",
            "title": "Importid"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "groupName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Groupname"
          },
          "fileName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Filename"
          },
          "fileFormat": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Fileformat"
          }
        },
        "type": "object",
        "required": [
          "importId",
          "status",
          "groupName",
          "fileName",
          "fileFormat"
        ],
        "title": "PublicImportStatus"
      },
      "PublicInputSlot": {
        "properties": {
          "node": {
            "type": "string",
            "title": "Node",
            "description": "The stable input-node id you bind to."
          },
          "flow": {
            "$ref": "#/components/schemas/Flow"
          },
          "moleculeType": {
            "$ref": "#/components/schemas/MoleculeClass"
          },
          "requiresStructure": {
            "type": "boolean",
            "title": "Requiresstructure"
          },
          "label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Label"
          },
          "chains": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Chains"
          },
          "chainLabels": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object",
            "title": "Chainlabels"
          },
          "residueFields": {
            "items": {
              "$ref": "#/components/schemas/PublicResidueField"
            },
            "type": "array",
            "title": "Residuefields"
          }
        },
        "type": "object",
        "required": [
          "node",
          "flow",
          "moleculeType",
          "requiresStructure",
          "label",
          "chains",
          "chainLabels",
          "residueFields"
        ],
        "title": "PublicInputSlot"
      },
      "PublicMolecule": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Type"
          },
          "entity": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object",
            "title": "Entity"
          },
          "chainMapping": {
            "additionalProperties": {
              "$ref": "#/components/schemas/MoleculeChainInfo"
            },
            "type": "object",
            "title": "Chainmapping"
          },
          "files": {
            "additionalProperties": {
              "items": {
                "$ref": "#/components/schemas/MoleculeFileEntry"
              },
              "type": "array"
            },
            "type": "object",
            "title": "Files"
          },
          "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata"
          },
          "truncated": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Truncated"
          },
          "createdAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Createdat"
          },
          "addedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Addedat"
          },
          "origin": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicMoleculeOrigin"
              },
              {
                "type": "null"
              }
            ]
          },
          "source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Source"
          },
          "groups": {
            "items": {
              "$ref": "#/components/schemas/PublicGroup"
            },
            "type": "array",
            "title": "Groups"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "entity",
          "chainMapping",
          "files",
          "metadata",
          "truncated",
          "createdAt",
          "addedAt",
          "origin",
          "source",
          "groups"
        ],
        "title": "PublicMolecule",
        "description": "One molecule, everything inline — no follow-up call to read scores."
      },
      "PublicMoleculeBinding": {
        "properties": {
          "flow": {
            "type": "string",
            "const": "molecule",
            "title": "Flow"
          },
          "group": {
            "type": "string",
            "title": "Group",
            "description": "A molecules group id (from the public molecules API)."
          },
          "chainMapping": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Chainmapping",
            "description": "referenceChain -> submitterChain (keyed by the template's reference label)."
          },
          "residuesByChain": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Residuesbychain",
            "description": "referenceChain -> residue selection."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "flow",
          "group"
        ],
        "title": "PublicMoleculeBinding"
      },
      "PublicMoleculeInput": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/MoleculeType"
              },
              {
                "type": "null"
              }
            ]
          },
          "entity": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object",
            "maxProperties": 64,
            "minProperties": 1,
            "title": "Entity"
          },
          "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "maxItems": 64,
            "title": "Tags"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "entity"
        ],
        "title": "PublicMoleculeInput",
        "description": "The spec's `MoleculeInput` — one molecule to create."
      },
      "PublicMoleculeOrigin": {
        "properties": {
          "type": {
            "type": "string",
            "title": "Type"
          },
          "jobId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Jobid"
          },
          "jobType": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Jobtype"
          }
        },
        "type": "object",
        "required": [
          "type",
          "jobId",
          "jobType"
        ],
        "title": "PublicMoleculeOrigin",
        "description": "`Molecule.origin` — how this molecule came to exist.\n\n`type` is the `complexes.origin_type` provenance class and is ALWAYS present (an\nuploaded molecule is `user_import`). `jobId`/`jobType` name the producing job/batch\nfor a tool-produced molecule; BOTH are null for an upload — an upload has no job, and\nwe do not fabricate one. Read-only, projected from columns that already exist (no\nmigration)."
      },
      "PublicMoleculePage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicMolecule"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "title": "PublicMoleculePage",
        "description": "The spec's `MoleculePage` — `{items, nextCursor}`, nothing else.\n\nDeliberately NOT `Page[PublicMolecule]`: the generic carries a third field\n(`sortedServerSide`, the internal sheet's score-cap fallback flag) that the\npublished contract doesn't declare and no public caller can act on."
      },
      "PublicPublishRequest": {
        "properties": {
          "version": {
            "type": "string",
            "minLength": 1,
            "title": "Version",
            "description": "The version id to publish (from a response's version.id / templateVersion.id)."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "version"
        ],
        "title": "PublicPublishRequest",
        "example": {
          "version": "3f2a1c9e-8b7d-4e6f-a1b2-c3d4e5f60718"
        }
      },
      "PublicPublishResponse": {
        "properties": {
          "templateId": {
            "type": "string",
            "title": "Templateid"
          },
          "isPublished": {
            "type": "boolean",
            "title": "Ispublished"
          },
          "publishedVersion": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicVersionRef"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "templateId",
          "isPublished",
          "publishedVersion"
        ],
        "title": "PublicPublishResponse"
      },
      "PublicRemoveMoleculesFromGroupRequest": {
        "properties": {
          "groupId": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Groupid"
          },
          "moleculeIds": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "maxItems": 1000,
            "minItems": 1,
            "title": "Moleculeids"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "groupId",
          "moleculeIds"
        ],
        "title": "PublicRemoveMoleculesFromGroupRequest",
        "description": "Body for `POST /molecules/remove`: the group to detach FROM plus the molecules\nto detach — the group id travels in the body alongside the ids. Same `moleculeIds` cap (maxItems, a real\nstatement bound — every id is a bound `uuid[]` parameter, never inlined)."
      },
      "PublicRemoveMoleculesResponse": {
        "properties": {
          "removedIds": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Removedids"
          },
          "removedCount": {
            "type": "integer",
            "title": "Removedcount"
          }
        },
        "type": "object",
        "required": [
          "removedIds",
          "removedCount"
        ],
        "title": "PublicRemoveMoleculesResponse"
      },
      "PublicResidueField": {
        "properties": {
          "node": {
            "type": "string",
            "title": "Node"
          },
          "field": {
            "type": "string",
            "title": "Field"
          },
          "multichain": {
            "type": "boolean",
            "title": "Multichain"
          },
          "targetsChains": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Targetschains"
          }
        },
        "type": "object",
        "required": [
          "node",
          "field",
          "multichain",
          "targetsChains"
        ],
        "title": "PublicResidueField"
      },
      "PublicRun": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "templateId": {
            "type": "string",
            "title": "Templateid"
          },
          "templateVersion": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicVersionRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "startedAt": {
            "type": "string",
            "title": "Startedat"
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Completedat"
          },
          "inputs": {
            "additionalProperties": true,
            "type": "object",
            "title": "Inputs",
            "description": "The recorded inputs (input-node id -> {group} or {file})."
          },
          "steps": {
            "items": {
              "$ref": "#/components/schemas/PublicStep"
            },
            "type": "array",
            "title": "Steps"
          }
        },
        "type": "object",
        "required": [
          "id",
          "templateId",
          "templateVersion",
          "name",
          "source",
          "status",
          "startedAt",
          "completedAt",
          "inputs",
          "steps"
        ],
        "title": "PublicRun"
      },
      "PublicRunConfig": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "source": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Source"
              },
              {
                "type": "null"
              }
            ],
            "default": "production"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicRunConfig"
      },
      "PublicRunPage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicRunSummary"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "title": "PublicRunPage"
      },
      "PublicRunSummary": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "templateId": {
            "type": "string",
            "title": "Templateid"
          },
          "templateVersion": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicVersionRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "startedAt": {
            "type": "string",
            "title": "Startedat"
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Completedat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "templateId",
          "templateVersion",
          "name",
          "source",
          "status",
          "startedAt",
          "completedAt"
        ],
        "title": "PublicRunSummary"
      },
      "PublicSchema": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "fields": {
            "items": {
              "$ref": "#/components/schemas/PublicSchemaField"
            },
            "type": "array",
            "title": "Fields"
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "fields",
          "createdAt"
        ],
        "title": "PublicSchema",
        "description": "The spec's `Schema`."
      },
      "PublicSchemaField": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "type": {
            "$ref": "#/components/schemas/PublicFieldType",
            "default": "string"
          },
          "required": {
            "type": "boolean",
            "title": "Required",
            "default": false
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "units": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Units"
          },
          "options": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array",
                "maxItems": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Options",
            "description": "Allowed values for a `category` field. REQUIRED (non-empty) when `type` is `category`, and must be omitted for every other type."
          },
          "chainType": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicChainType"
              },
              {
                "type": "null"
              }
            ]
          },
          "chainTag": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicChainTag"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "name"
        ],
        "title": "PublicSchemaField",
        "description": "The spec's `SchemaField` — one field in a schema.\n\nA RequestModel (extra='forbid') even though it also appears on responses: the\nfield names are already the wire spelling, so no alias generator is needed, and\nforbidding extras means a typo'd `chaintype` 422s at create instead of being\nsilently stored in the JSONB and never enforced.\n\n`type` defaults to `string` per the spec. `chain` is one of its values — a\nchain field's `name` IS the chain id (`H`, `L`, `A`), matching the keys of a\nmolecule's `entity` map."
      },
      "PublicSchemaPage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicSchema"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "title": "PublicSchemaPage"
      },
      "PublicStep": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "node": {
            "type": "string",
            "title": "Node",
            "description": "The stable IR node id."
          },
          "label": {
            "type": "string",
            "title": "Label"
          },
          "type": {
            "type": "string",
            "title": "Type"
          },
          "status": {
            "$ref": "#/components/schemas/StepStatus"
          },
          "startedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Startedat"
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Completedat"
          },
          "outputCount": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Outputcount"
          },
          "jobsTotal": {
            "type": "integer",
            "title": "Jobstotal"
          },
          "jobsComplete": {
            "type": "integer",
            "title": "Jobscomplete"
          },
          "outputGroup": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Outputgroup",
            "description": "The molecules group id this step produced."
          }
        },
        "type": "object",
        "required": [
          "id",
          "node",
          "label",
          "type",
          "status",
          "startedAt",
          "completedAt",
          "outputCount",
          "jobsTotal",
          "jobsComplete",
          "outputGroup"
        ],
        "title": "PublicStep"
      },
      "PublicSubmitRunRequest": {
        "properties": {
          "version": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Version",
            "description": "A version id (from a response); absent -> published-then-latest."
          },
          "bindings": {
            "additionalProperties": {
              "$ref": "#/components/schemas/PublicBinding"
            },
            "type": "object",
            "title": "Bindings",
            "description": "One binding per input slot, keyed by the slot's input-node id."
          },
          "config": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicRunConfig"
              },
              {
                "type": "null"
              }
            ]
          },
          "idempotencyKey": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 255,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Idempotencykey",
            "description": "Client key to safely retry a submit (≤255)."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "bindings"
        ],
        "title": "PublicSubmitRunRequest",
        "example": {
          "bindings": {
            "target": {
              "flow": "molecule",
              "group": "3f8a1c2e-5b7d-4e9f-a1b2-c3d4e5f6a7b8"
            }
          },
          "config": {
            "name": "run-1",
            "source": "production"
          },
          "version": "8f14e45f-ceea-467d-9a3c-2b7a1e2c9d10"
        }
      },
      "PublicTemplate": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "isPublished": {
            "type": "boolean",
            "title": "Ispublished"
          },
          "publishedVersion": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicVersionRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "version": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicVersionRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "pipeline": {
            "$ref": "#/components/schemas/PipelineIR"
          },
          "inputs": {
            "items": {
              "$ref": "#/components/schemas/PublicInputSlot"
            },
            "type": "array",
            "title": "Inputs"
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          },
          "updatedAt": {
            "type": "string",
            "title": "Updatedat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "isPublished",
          "publishedVersion",
          "version",
          "pipeline",
          "inputs",
          "createdAt",
          "updatedAt"
        ],
        "title": "PublicTemplate"
      },
      "PublicTemplatePage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicTemplateSummary"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "title": "PublicTemplatePage"
      },
      "PublicTemplateSummary": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "isPublished": {
            "type": "boolean",
            "title": "Ispublished"
          },
          "versionCount": {
            "type": "integer",
            "title": "Versioncount"
          },
          "runCount": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Runcount"
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          },
          "updatedAt": {
            "type": "string",
            "title": "Updatedat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "isPublished",
          "versionCount",
          "runCount",
          "createdAt",
          "updatedAt"
        ],
        "title": "PublicTemplateSummary"
      },
      "PublicUnchecked": {
        "properties": {
          "tier": {
            "$ref": "#/components/schemas/ValidationTier"
          },
          "reason": {
            "type": "string",
            "title": "Reason"
          }
        },
        "type": "object",
        "required": [
          "tier",
          "reason"
        ],
        "title": "PublicUnchecked",
        "description": "One tier the verdict did NOT evaluate, and why. `reason` is server-authored prose (never an\nengine/validator passthrough — same firewall rule as `PublicDiagnostic.details`)."
      },
      "PublicUpdateMetadataRequest": {
        "properties": {
          "properties": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Properties"
          },
          "source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Source"
          },
          "fileId": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Fileid"
          },
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "metadata": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Metadata",
            "description": "DEPRECATED alias for `properties`, kept for callers written against the original v1 shape. Send `properties` instead; if both are sent, `properties` wins."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicUpdateMetadataRequest",
        "description": "The spec's `UpdateMoleculeRequest` — a PARTIAL patch across the MOLECULE and\nMEMBERSHIP tenancy grains, applied ATOMICALLY (all-or-nothing, one transaction: if any\nfield's write fails, none of them persist).\n\nEvery field is OPTIONAL and independent: an ABSENT field is left untouched, which\nis DISTINCT from a field sent as `null` (which CLEARS it, where the grain allows).\nThe fields and the grain each writes:\n\n  * `properties` (MOLECULE grain) — merge scalar annotations into the molecule's\n    metadata. Only the keys you send change; a key set to `null` REMOVES that key\n    (an absent key and a null key mean different things, so the raw dict carries\n    intent). Tool score-run entries are written by tools and are not editable here;\n    when the molecule's group is schema-bound the MERGED result must still satisfy\n    it. This is the properties-only PATCH's behaviour, unchanged.\n  * `source` (MOLECULE grain) — the id of the PARENT molecule this one was derived\n    from; `null` clears it. The parent must be visible in YOUR scope, or the write\n    is refused (404) — you cannot point a molecule at a parent you cannot see.\n  * `fileId` (MOLECULE + MEMBERSHIP grain) — a structure file to associate as this\n    membership's primary; `null` clears it. The file must already be attached to\n    this molecule in your tenant.\n  * `name` (MEMBERSHIP grain) — rename this molecule's per-group display name. A\n    name already used by another molecule in the same group is a 409 conflict (the\n    `UNIQUE (group_id, name)` constraint), never a 500.\n\nThe membership-grained writes (`name`, `fileId`) target the molecule's MOST RECENT\nin-scope group membership, matching how the group-less by-id read resolves labels."
      },
      "PublicUpdateSchemaRequest": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "fields": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/PublicSchemaField"
                },
                "type": "array",
                "maxItems": 200,
                "minItems": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Fields"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicUpdateSchemaRequest",
        "description": "The spec's `UpdateSchemaRequest` — partial. `fields` REPLACES the whole\nlist; omitted keys are left unchanged."
      },
      "PublicUploadRequest": {
        "properties": {
          "groupId": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Groupid"
          },
          "chainMapping": {
            "additionalProperties": {
              "$ref": "#/components/schemas/PublicChainMappingEntry"
            },
            "type": "object",
            "title": "Chainmapping"
          },
          "molecules": {
            "items": {
              "$ref": "#/components/schemas/PublicMoleculeInput"
            },
            "type": "array",
            "maxItems": 1000,
            "minItems": 1,
            "title": "Molecules"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "groupId",
          "molecules"
        ],
        "title": "PublicUploadRequest",
        "description": "The spec's `UploadRequest`."
      },
      "PublicUploadResponse": {
        "properties": {
          "groupId": {
            "type": "string",
            "title": "Groupid"
          },
          "groupName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Groupname"
          },
          "moleculeIds": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Moleculeids"
          },
          "moleculeCount": {
            "type": "integer",
            "title": "Moleculecount"
          },
          "status": {
            "type": "string",
            "enum": [
              "ingested",
              "pending"
            ],
            "title": "Status",
            "default": "ingested"
          },
          "importId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Importid"
          }
        },
        "type": "object",
        "required": [
          "groupId",
          "groupName",
          "moleculeIds",
          "moleculeCount"
        ],
        "title": "PublicUploadResponse"
      },
      "PublicValidateResponse": {
        "properties": {
          "valid": {
            "type": "boolean",
            "title": "Valid"
          },
          "diagnostics": {
            "items": {
              "$ref": "#/components/schemas/PublicDiagnostic"
            },
            "type": "array",
            "title": "Diagnostics"
          },
          "reachedTier": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ValidationTier"
              },
              {
                "type": "null"
              }
            ]
          },
          "unchecked": {
            "items": {
              "$ref": "#/components/schemas/PublicUnchecked"
            },
            "type": "array",
            "title": "Unchecked"
          }
        },
        "type": "object",
        "required": [
          "valid",
          "diagnostics"
        ],
        "title": "PublicValidateResponse"
      },
      "PublicVersionPage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicVersionSummary"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "title": "PublicVersionPage"
      },
      "PublicVersionRef": {
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "title": "Id",
            "description": "The version id; echo back verbatim to act on this version."
          },
          "displayName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Displayname",
            "description": "Human label e.g. 'v3' (None for pre-versioning rows); NOT an identifier."
          },
          "displayOrdinal": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Displayordinal",
            "description": "1-based display position (matches /versions order); NOT an identifier."
          }
        },
        "type": "object",
        "required": [
          "id",
          "displayName",
          "displayOrdinal"
        ],
        "title": "PublicVersionRef",
        "description": "A version reference.\n\n`id` is THE identifier — the version's stable id (opaque, unguessable, org-scoped on every read):\necho it back verbatim to submit/publish/inputs. There is no synthesized `vN` handle to resolve, so\nidentity resolution is identity — a given id either exists on read or 404s. `displayName` ('v3')\nand `displayOrdinal` (3) are OUTPUT-ONLY human labels; they are never accepted as input, so they\ncannot drift into disagreement (nothing consumes them for identity — the reason the old\nname/ordinal handle pair was a recurring defect class)."
      },
      "PublicVersionSummary": {
        "properties": {
          "version": {
            "$ref": "#/components/schemas/PublicVersionRef"
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          },
          "isPublished": {
            "type": "boolean",
            "title": "Ispublished"
          },
          "isValid": {
            "type": "boolean",
            "title": "Isvalid"
          }
        },
        "type": "object",
        "required": [
          "version",
          "createdAt",
          "isPublished",
          "isValid"
        ],
        "title": "PublicVersionSummary"
      },
      "RunStatus": {
        "type": "string",
        "enum": [
          "queued",
          "running",
          "finished",
          "partial",
          "stopped",
          "failed"
        ],
        "title": "RunStatus"
      },
      "Severity": {
        "type": "string",
        "enum": [
          "error",
          "warning"
        ],
        "title": "Severity"
      },
      "Source": {
        "type": "string",
        "enum": [
          "test",
          "production"
        ],
        "title": "Source"
      },
      "StepStatus": {
        "type": "string",
        "enum": [
          "waiting",
          "queued",
          "running",
          "finished",
          "failed",
          "skipped",
          "stopped",
          "cancelled"
        ],
        "title": "StepStatus"
      },
      "ValidationTier": {
        "type": "string",
        "enum": [
          "shape",
          "graph",
          "fit",
          "runtime"
        ],
        "title": "ValidationTier",
        "description": "How deep a validation verdict reached. `shape` / `graph` / `fit` are the STATIC tiers the\nserver evaluates in order; `runtime` is the engine's executable resolution pass. The pre-submit\ncheck (`POST /templates/{id}/validate`) runs the runtime tier too — a `reachedTier: runtime`\nverdict means the engine resolved the run, the strongest promise this surface gives. The tier\nFAILS OPEN (if the engine is unreachable the static verdict still returns), so when it can't run\nthe verdict reports `runtime` under `unchecked` instead — never a false pass. A verdict names the\ntier it reached and lists the ones it could not."
      },
      "PublicProblem": {
        "description": "RFC 9457 problem detail. Serialized as `application/problem+json` on every public error.",
        "properties": {
          "type": {
            "description": "A URI identifying the error kind (dereferenceable docs).",
            "title": "Type",
            "type": "string"
          },
          "title": {
            "description": "A short, human-readable summary of the error kind.",
            "title": "Title",
            "type": "string"
          },
          "status": {
            "description": "The HTTP status code, duplicated in the body per RFC 9457.",
            "title": "Status",
            "type": "integer"
          },
          "code": {
            "description": "A stable machine-readable slug; switch on THIS, not prose.",
            "title": "Code",
            "type": "string"
          },
          "detail": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Instance-specific human explanation.",
            "title": "Detail"
          },
          "errors": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Structured per-item detail (request-validation fields OR pipeline diagnostics).",
            "title": "Errors"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "title": "PublicProblem",
        "type": "object"
      },
      "JobSubmission": {
        "type": "object",
        "required": [
          "jobName",
          "type",
          "settings"
        ],
        "properties": {
          "jobName": {
            "type": "string",
            "description": "Name for the job, unique within your account. Characters outside [A-Za-z0-9_.- ] are stripped and whitespace becomes underscores, so a name is sanitised rather than rejected.",
            "minLength": 1,
            "example": "my-protein-analysis"
          },
          "type": {
            "type": "string",
            "description": "Tool to run. The available tools are account-scoped — fetch the live list from `GET /tools` rather than assuming a name.",
            "example": "alphafold"
          },
          "settings": {
            "type": "object",
            "additionalProperties": true,
            "description": "Tool-specific settings. The accepted fields differ per tool and per account, so they are not enumerated here: fetch the JSON Schema for the tool you are submitting from `GET /tools/{name}/schema` and validate against that. `POST /validate-job` checks a payload for free.",
            "example": {
              "sequence": "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQ"
            }
          }
        }
      },
      "BatchSubmission": {
        "type": "object",
        "required": [
          "batchName",
          "type",
          "settings"
        ],
        "properties": {
          "batchName": {
            "type": "string",
            "description": "Name for the batch, unique within your account. A batch name is held by a lock for 15 minutes after submission, so reusing one within that window returns 409.",
            "minLength": 1,
            "example": "my-batch-analysis"
          },
          "type": {
            "type": "string",
            "description": "Tool to run for every job in the batch.",
            "example": "alphafold"
          },
          "settings": {
            "type": "array",
            "description": "One settings object per job — the array form is what distinguishes this endpoint from `/submit-job`. See `JobSubmission.settings` for where the per-tool shape comes from.",
            "minItems": 1,
            "maxItems": 30000,
            "items": {
              "$ref": "#/components/schemas/JobSubmission/properties/settings"
            }
          },
          "jobNames": {
            "type": "array",
            "description": "Optional names, the same length as `settings`. Omit to have jobs auto-named. If any name collides with an existing job, every name is rewritten as `{batchName}-{name}`.",
            "minItems": 1,
            "maxItems": 30000,
            "items": {
              "$ref": "#/components/schemas/JobSubmission/properties/jobName"
            }
          }
        }
      },
      "JobResponse": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "Response message",
            "example": "Job submitted successfully"
          }
        }
      },
      "BatchResponse": {
        "type": "object",
        "properties": {
          "batchName": {
            "type": "string",
            "description": "Name of the submitted batch"
          },
          "jobs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JobResponse"
            }
          },
          "totalJobs": {
            "type": "integer",
            "description": "Total number of jobs in the batch"
          }
        }
      },
      "JobInfo": {
        "type": "object",
        "properties": {
          "JobName": {
            "type": "string",
            "description": "Name of the job"
          },
          "Type": {
            "type": "string",
            "description": "Tool used for the job"
          },
          "JobStatus": {
            "type": "string",
            "enum": [
              "Complete",
              "In Queue",
              "Running",
              "Stopped",
              "Deleted"
            ],
            "description": "Current status of the job"
          },
          "Created": {
            "type": "string",
            "format": "date-time",
            "description": "When the job was created"
          },
          "Started": {
            "type": "string",
            "format": "date-time",
            "description": "When the job started (if applicable)"
          },
          "Completed": {
            "type": "string",
            "format": "date-time",
            "description": "When the job completed (if applicable)"
          },
          "Settings": {
            "type": "object",
            "description": "Job settings and parameters (stored as JSON string)",
            "additionalProperties": true
          },
          "Score": {
            "type": "number",
            "description": "Job score (if applicable)"
          },
          "Batch": {
            "type": "boolean",
            "description": "Whether this is a batch job"
          },
          "User": {
            "type": "string",
            "description": "User who created the job (only present in organization queries)"
          },
          "batchStatus": {
            "type": "string",
            "enum": [
              "Running",
              "Aggregating",
              "Complete",
              "Stopped",
              "AggregationFailed"
            ],
            "description": "Batch-level phase. Present only on a batch's parent job — fetch it with ?jobName=<batchName>. The individual subjobs report Complete as soon as they finish computing, but the batch then spends a few minutes aggregating their results into the final output files. Poll this field instead: `Aggregating` means the output is still being prepared, and `Complete` means the aggregated batch output is ready to download. `AggregationFailed` means aggregation errored (see AggregationError)."
          },
          "AggregationError": {
            "type": "string",
            "description": "Error message, present when batchStatus is AggregationFailed"
          },
          "resultUrl": {
            "type": "string",
            "description": "Present on a single-job lookup (jobName=<batchName>) of a batch parent once batchStatus is Complete: a pre-signed URL (valid ~1 hour) that downloads the aggregated output directly — no extra call or API key needed. Omitted from list responses."
          },
          "aggregateStatus": {
            "type": "string",
            "enum": [
              "InProgress",
              "Complete",
              "Failed"
            ],
            "description": "Status of the on-demand batch-aggregate worker that builds the result zip when the natural-completion path didn't (large batches, retries, etc.). Present only on a single-job lookup (jobName=<batchName>) when a worker exists for this caller and parent. `InProgress` — the zip is being built; wait for `resultUrl` to appear, then download. `Complete` — the worker finished; `resultUrl` should be present. `Failed` — retry /result to spawn a new worker."
          },
          "aggregateJobName": {
            "type": "string",
            "description": "Name of the batch-aggregate worker job (when aggregateStatus is set). Can be polled directly via /jobs?jobName=<aggregateJobName> to watch JobStatus."
          }
        }
      },
      "JobResult": {
        "type": "object",
        "properties": {
          "jobName": {
            "type": "string",
            "description": "Name of the job"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed",
              "failed",
              "cancelled"
            ],
            "description": "Current status of the job"
          },
          "results": {
            "type": "object",
            "description": "Job results (structure varies by tool)",
            "additionalProperties": true
          },
          "error": {
            "type": "string",
            "description": "Error message if job failed"
          },
          "outputFiles": {
            "type": "array",
            "description": "List of output files",
            "items": {
              "type": "object",
              "properties": {
                "fileName": {
                  "type": "string",
                  "description": "Name of the output file"
                },
                "fileUrl": {
                  "type": "string",
                  "description": "URL to download the file"
                },
                "fileType": {
                  "type": "string",
                  "description": "Type of the file (PDB, JSON, etc.)"
                }
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error message"
          },
          "detail": {
            "type": "string",
            "description": "Error message (V2 alias — same text as `error`, for FastAPI DomainException compatibility)"
          },
          "code": {
            "type": "string",
            "description": "Error code"
          },
          "details": {
            "type": "object",
            "description": "Additional error details"
          }
        }
      },
      "ToolInfo": {
        "type": "object",
        "description": "One tool in the catalogue. `settings` describes its parameters; fetch `GET /tools/{name}/schema` for the same information as JSON Schema.",
        "properties": {
          "name": {
            "type": "string",
            "description": "The value to send as `type` when submitting.",
            "example": "alphafold"
          },
          "displayName": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "github": {
            "type": "string"
          },
          "paper": {
            "type": "string"
          },
          "settings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                },
                "type": {
                  "type": "string",
                  "description": "Present only for a subset of parameter kinds — read it defensively, and prefer the JSON Schema from `/tools/{name}/schema`."
                },
                "description": {
                  "type": "string"
                },
                "options": {
                  "type": "array",
                  "items": {}
                },
                "default": {}
              }
            }
          }
        }
      },
      "ValidationResult": {
        "type": "object",
        "required": [
          "valid"
        ],
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "normalized": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present when valid — the settings to submit, with defaults filled in."
          },
          "error": {
            "type": "string",
            "description": "Present when invalid — the first problem found."
          },
          "missing_fields": {
            "type": "array",
            "description": "Best-effort list of required inputs still missing. May be empty even when `valid` is false, because validation stops at the first error.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "displayName": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "example": {}
              }
            }
          }
        }
      },
      "PipelineStage": {
        "type": "object",
        "required": [
          "task",
          "toolSettings"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Ignored on submit. The server assigns each stage a 1-based index and uses that in error messages, overwriting anything sent here."
          },
          "task": {
            "type": "string",
            "description": "What this stage does, e.g. \"Structure Prediction\"."
          },
          "tools": {
            "type": "array",
            "description": "Optional and derived — submission overwrites it with the keys of `toolSettings` (submit-pipeline.js), so an empty array is accepted. It only widens the set checked against your account's tool access, so naming a tool here without settings for it grants nothing.",
            "items": {
              "type": "string"
            }
          },
          "toolSettings": {
            "type": "object",
            "additionalProperties": true,
            "minProperties": 1,
            "description": "Settings per tool, keyed by tool name — this is what determines which tools the stage runs, so it may not be empty. Each value follows that tool's schema from `GET /tools/{name}/schema`."
          },
          "scoringTools": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "scoringToolSettings": {
            "type": "object",
            "additionalProperties": true
          },
          "filterSettings": {
            "type": "object",
            "additionalProperties": true,
            "description": "Metric filters applied to this stage's outputs. Metric names are case-sensitive and tool-specific; an unknown one is rejected with the valid options listed."
          }
        }
      },
      "DeployedModel": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Use this as the `type` when submitting a job."
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "created": {
            "type": "string"
          },
          "gpu": {
            "type": "boolean"
          },
          "environment": {
            "type": "string"
          },
          "entrypoint": {
            "type": "string"
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "testUrl": {
            "type": "string",
            "description": "Present on deploy — a page for trying the model."
          },
          "email": {
            "type": "string",
            "description": "Present only when the model belongs to another member of your organization."
          }
        }
      },
      "FinetunedModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Use this as the `modelName` when submitting."
          },
          "type": {
            "type": "string"
          },
          "inferenceType": {
            "type": [
              "string",
              "null"
            ]
          },
          "baseModel": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "created": {
            "type": "string"
          },
          "owner": {
            "type": "string",
            "description": "Present only when the model belongs to another member of your organization."
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "API key missing or invalid.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        }
      },
      "NotFound": {
        "description": "The addressed resource does not exist, or is not visible to your tenant.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        }
      },
      "ValidationProblem": {
        "description": "The request was malformed or failed validation; see `errors` for the offending fields.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        }
      },
      "Error": {
        "description": "An error occurred (RFC 9457 problem+json). Switch on the stable `code`, not on prose.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Jobs",
      "description": "Job submission and management"
    },
    {
      "name": "Tools",
      "description": "The tool catalogue"
    },
    {
      "name": "Files",
      "description": "File upload and management"
    },
    {
      "name": "Results",
      "description": "Job results retrieval"
    },
    {
      "name": "Models",
      "description": "Finetuned models"
    },
    {
      "name": "Usage",
      "description": "Usage statistics"
    },
    {
      "name": "Pipelines",
      "description": "Legacy saved pipelines"
    }
  ]
}