{
  "openapi": "3.1.0",
  "info": {
    "title": "Tamarind Bio API",
    "version": "1.0",
    "description": "The complete Tamarind Bio public API.\n\n- **Submission, tools, results, and files** — under `/api`. Submit and manage runs of any tool in the catalog.\n- **Jobs reads** — under `/api/jobs`, preserving the established request and response contract.\n- **Pipelines and molecules** — typed resources under `/api/pipelines` and `/api/molecules`.\n\n**Use the host that served this spec.** `servers` is set to the deployment you\nfetched from. An organisation with a dedicated deployment keeps its jobs, files and\nAPI keys in its own account, reachable only from that host — and sending to the\nshared host from such an account does NOT error: the request succeeds and the work\nlands where that user will never see it.\n\nAlmost every request authenticates with an `x-api-key` header — get one at\n`/api-docs/api-key`. **Route families answer a bad key differently, so branch on\n\"not 2xx\" rather than on a status.** The classic submit/tools/files routes answer\n**400** with a JSON recovery body. The `/api/jobs` application contract declares **401**\nwith an RFC 9457 `application/problem+json` body. Jobs, pipelines and molecules share\ngateway authentication, which can reject bad credentials earlier with a generic **403**.\n\nSix classic routes are themselves exceptions to the 400 rule. `GET /models`,\n`GET /finetuned-models` and `GET /usage-statistics` answer **401** with a bare scalar\n(`-1`, `Unauthenticated`, `Unauthorized`) and no recovery fields. `GET /projects/list`\nand `POST /projects/create` answer **401** as `{\"error\": \"Unauthorized\"}`, also with no\nrecovery fields — they are session-first UI routes that gained the api-key path, and the\nbrowser relies on that status. `PUT /upload/{filename}`\nis not served by the API layer at all — it redirects to a CloudFront host, so an\nunauthenticated caller receives the redirect rather than any JSON body.\n\n**Exception: `GET /api/tools-catalog` (also `/tools.json`, `/tools.md`) needs NO key.**\nIt lists every publicly submittable tool `type` with its required settings, so a correct\npayload can be built before an account exists. This overview previously said every\nrequest needs a key, which sent agents away before trying the one open door."
  },
  "servers": [
    {
      "url": "https://structure-prediction-fpy829tfg-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. The body is PLAIN TEXT, not JSON, and carries no job id — literally `<jobName> submitted to queue.` A generated client that calls .json() on this response throws on the HAPPY path. POLL THE NAME ECHOED IN THIS BODY, not the one you sent. `jobName` is NORMALIZED rather than validated: characters outside [A-Za-z0-9_.-] are stripped and whitespace becomes `_`, so submitting `my run!` stores `my_run` and polling `my run!` afterwards reports an unknown job. Parse the name out of this response and use that as the handle, or pre-normalize before submitting so the two cannot differ.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "my-protein-analysis submitted to queue."
                }
              }
            }
          },
          "400": {
            "description": "Bad request. TWO body shapes share this status code, and you cannot tell them apart by `Content-Type`: Next's `send()` emits a string body with NO `Content-Type` at all (see `sendSubmitted` in `pages/api/submit-job.js`). Try to parse the body as JSON — if it parses you have an `ErrorResponse`; if it does not, the body IS the message, in full.\nWhich rejections are JSON is NOT enumerated here, and deliberately: the list has been wrong every time it was written down. Parse, then read what you got. A JSON body may carry a machine-readable flag beside `error` — `availableProjects` on the required-project refusal, `getApiKey`/`agentGuide`/`toolCatalog` on an API-key rejection, `designCountTooLarge` and `batchingUnsupported` on custom-tool design caps — so branch on the FIELD you need rather than on a status or a media type. Most rejections are still a bare string, and for those the body is the whole message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Missing required field \"jobName\""
                }
              }
            }
          }
        },
        "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. TWO body shapes share this status code — see `/submit-job` for how to tell them apart (parse, do not read `Content-Type`) and for the flags a JSON rejection can carry. Most rejections here are a bare string.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Missing required field \"batchName\""
                }
              }
            }
          }
        },
        "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 will return. Wait, then repeat the same /result call.",
            "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-catalog": {
      "get": {
        "summary": "List every tool name — no API key required",
        "description": "The one endpoint on this surface that needs no credentials, so an agent can confirm a tool name is real BEFORE the user has an account. Also served at `/tools.json` (and `/tools.md`) **on this same host** — this `/api/tools-catalog` path is the same handler and is documented here so it is reachable from the spec. Follow it on the host that served this spec: the catalog names submit and schema endpoints for whichever deployment answered, so fetching it from another host hands you that host's endpoints.\n\nReturns the exact, case-sensitive `type` string to send to `/submit-job`, plus each tool's REQUIRED settings. Guessing a name is the most common way generated code fails, and an unrecognised settings key is never rejected, only flagged — so a synonym surfaces as \"missing required field\", not as an unknown-key error.\n\nDeliberately NOT the whole schema: optional parameters, defaults and descriptions need a key (`/tools/{name}/schema`). Unlike `/tools`, this is not account-scoped — it lists what a brand-new public user could submit, so feature-flagged, domain-restricted and API-gated tools are omitted.",
        "operationId": "getPublicToolCatalog",
        "security": [],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "Return only this tool, so a caller that already knows the name does not have to pull the whole catalogue into context.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Return only tools carrying this intent tag (case-insensitive), e.g. `protein-ligand-docking`. The full tag list is in the `tags` field of every response.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "`md` returns the catalogue as a Markdown table (`text/markdown`) instead of JSON.",
            "schema": {
              "type": "string",
              "enum": [
                "md"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The public tool catalogue",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "$comment": {
                      "type": "string",
                      "description": "How to read `requiredSettings` — that requiredness is usually conditional, which key the `tasks` predicates refer to, and what the shape-bearing fields mean. Read it before treating the list as a checklist. Declared here because a typed generator exposes only declared properties, and dropping it drops the instructions."
                    },
                    "submitEndpoint": {
                      "type": "string",
                      "description": "The absolute submit URL, on the host this response was fetched from, with its auth requirement stated. Every route lives under `/api/`; `POST /submit-job` is a 404.",
                      "example": "POST https://<the-host-that-served-this>/api/submit-job (requires x-api-key)"
                    },
                    "schemaEndpoint": {
                      "type": "string",
                      "description": "The per-tool full-schema URL. Needs a key."
                    },
                    "validateEndpoint": {
                      "type": "string",
                      "description": "Dry-run a payload without spending a job. Needs a key too — free of compute, not free of auth."
                    },
                    "agentGuide": {
                      "type": "string",
                      "description": "The complete agent guide, on the host this was fetched from."
                    },
                    "openapi": {
                      "type": "string",
                      "description": "This document, on the host this response was fetched from."
                    },
                    "mcpServer": {
                      "type": "string",
                      "description": "MCP endpoint. Not host-derived — there is no per-tenant MCP.",
                      "example": "https://mcp.tamarind.bio/mcp"
                    },
                    "filter": {
                      "type": "object",
                      "description": "Present only when `type` or `tag` narrowed the response.",
                      "properties": {
                        "type": {
                          "type": "string"
                        },
                        "tag": {
                          "type": "string"
                        }
                      }
                    },
                    "ignoredParamCount": {
                      "type": "integer",
                      "description": "How many query parameters this endpoint did NOT understand. Present only when at least one was ignored, so a request using only `type`, `tag` and `format` is byte-identical to before this field existed. Anything counted here was DROPPED, not applied: `?search=vina` returns the whole catalogue with `filter` absent. Treat a non-zero value as \"my filter did not happen\" and re-issue with `?type=` (exact tool name) or `?tag=` (category). Declared here for the same reason as `$comment`: a typed generator exposes only declared properties, so an undeclared warning is a warning the caller cannot see. The ignored NAMES are deliberately not echoed back — this response is read by agents, and reflecting caller-supplied text into it would make the endpoint a prompt-injection relay for anyone who can choose the URL."
                    },
                    "ignoredParamsNote": {
                      "type": "string",
                      "description": "The same fact in prose, for an agent consuming this document as text rather than as a typed object. Present exactly when `ignoredParamCount` is."
                    },
                    "count": {
                      "type": "integer",
                      "description": "Tools in this response, after any `type`/`tag` filter"
                    },
                    "totalCount": {
                      "type": "integer",
                      "description": "Tools in the unfiltered catalogue"
                    },
                    "tags": {
                      "type": "array",
                      "description": "Every intent tag in use, for the `tag` parameter",
                      "items": {
                        "type": "string"
                      }
                    },
                    "tools": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicToolInfo"
                      }
                    }
                  }
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Returned when `format=md`"
                }
              }
            }
          }
        },
        "tags": [
          "Tools"
        ]
      }
    },
    "/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": {
                    "$ref": "#/components/schemas/ProjectTag"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submitted. Body is the plain-text confirmation `Pipeline {jobName} submitted to queue.`",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "A missing/empty `stages`, a duplicate `jobName`, a stage with no task or tools, an unknown filter metric, or an unsupported tool — each a bare string, the message in full. An API-key rejection and the required-project refusal are JSON instead. See `/submit-job` for how to tell the two shapes apart — parse the body, do not read `Content-Type` — and for the flags a JSON rejection carries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Missing initial inputs"
                }
              }
            }
          },
          "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/projects/list": {
      "get": {
        "summary": "List your projects",
        "description": "Every project you can tag a job with: your organization's projects, your own personal ones, and any private project you have been invited to.\nRead this to resolve a `projectTag`. Only ORGANIZATION projects (`scope: \"org\"`) satisfy an org's \"require a project tag\" policy — a personal or shared project does not, and submitting with one is refused exactly as if you had sent none.",
        "operationId": "listProjects",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "includeCounts",
            "in": "query",
            "description": "Set to `true` to add `jobCount` to each project. This costs one extra query PER PROJECT, so leave it off unless you need the numbers.",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your visible projects.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "isInOrg": {
                      "type": "boolean",
                      "description": "Whether you belong to an organization."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/projects/create": {
      "post": {
        "summary": "Create a project",
        "description": "Create a project you can then pass as `projectTag`. Creates it under your ORGANIZATION by default, which is the scope that satisfies a \"require a project tag\" policy; pass `scope: \"personal\"` for a private one, which does not.\nCreating grants no privilege an interactive user does not already have — the key acts as you. If your organization curates its project list, prefer `GET /projects/list` and reuse an existing one rather than creating a near-duplicate.",
        "operationId": "createProject",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Project name, unique within the scope it is created in."
                  },
                  "description": {
                    "type": "string"
                  },
                  "color": {
                    "type": "string",
                    "description": "Hex colour for the UI. Omit to be assigned one at random."
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "org",
                      "personal"
                    ],
                    "description": "Defaults to `org`. `personal` is only honoured if you belong to an organization; otherwise every project is personal anyway."
                  },
                  "members": {
                    "type": "array",
                    "description": "Emails to invite. Only applies to a `personal`-scoped project — an org project is already visible to the whole org. Addresses outside your organization are dropped silently.",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  }
                }
              },
              "example": {
                "name": "Programme A",
                "description": "Q3 binder campaign"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/Project"
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "failedAdds": {
                      "type": "array",
                      "description": "Members that could not be invited. The project was still created.",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid name, colour or description.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "A project with this name already exists in that scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "tags": [
          "Projects"
        ]
      }
    },
    "/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/model-router": {
      "post": {
        "summary": "Recommend a tool for a task",
        "description": "Recommend which model to run, given your inputs and natural language prompt, backed by Tamarind benchmarks. Currently supports structure prediction, more tasks coming soon.",
        "operationId": "recommendTools",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicToolRecommendationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The recommendation. An abstention is also a 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicToolRecommendation"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ClassicPublicProblem"
                }
              }
            }
          },
          "403": {
            "description": "Tool recommendations are not enabled for this account.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ClassicPublicProblem"
                }
              }
            }
          },
          "413": {
            "description": "Request body is too large.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ClassicPublicProblem"
                }
              }
            }
          },
          "422": {
            "description": "One or more request fields are invalid.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ClassicPublicProblem"
                }
              }
            }
          },
          "502": {
            "description": "The recommendation service rejected an upstream request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ClassicPublicProblem"
                }
              }
            }
          },
          "503": {
            "description": "Tool recommendations are not available in this environment.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ClassicPublicProblem"
                }
              }
            }
          }
        },
        "tags": [
          "Tools"
        ]
      }
    },
    "/api/molecules/groups": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "List Groups",
        "description": "List your molecule groups.\n\nA group is a named collection of molecules. Pass `scope=org` to include your whole organization.\n\nPaginated — 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"
                }
              ],
              "description": "One condition on the group's own metadata, written `field:operator:value`. Repeat it and a group must pass every condition. Operators: `gt`, `gte`, `lt`, `lte`, `eq`, `neq`, `iequals`, `includes`, and `exists`, written `field:exists` with no value. A dotted field names a metadata key here, never a tool score (`stage.phase` reads the key `stage.phase`): groups carry no scores, so nothing on this endpoint reads one. At most 32 conditions.",
              "title": "Filter"
            },
            "description": "One condition on the group's own metadata, written `field:operator:value`. Repeat it and a group must pass every condition. Operators: `gt`, `gte`, `lt`, `lte`, `eq`, `neq`, `iequals`, `includes`, and `exists`, written `field:exists` with no value. A dotted field names a metadata key here, never a tool score (`stage.phase` reads the key `stage.phase`): groups carry no scores, so nothing on this endpoint reads one. At most 32 conditions."
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(mine|org)$",
              "description": "`mine` (the default) shows groups you created; `org` shows every group in your organization.",
              "default": "mine",
              "title": "Scope"
            },
            "description": "`mine` (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 for the first page; follow `nextCursor` until null (an empty `items` page while `nextCursor` is set is normal).",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit for the first page; follow `nextCursor` until null (an empty `items` page while `nextCursor` is set is normal)."
          }
        ],
        "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 molecule group.\n\nAdd molecules with `POST /molecules/upload` (JSON) or `POST /molecules/import-file` (a file).\n\nBind 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/groups/{group_id}/score-observations": {
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "List Group Score Observations",
        "description": "List retained seed/sample observations for explicit concrete jobs in this group.\n\nEvery requested job must be visible, concrete, eligible, terminally complete,\nand output-ingested. Every item carries its actual producing `jobId` and `jobName`.\n\nThis is an advanced, read-only API. It returns observations in bulk and does not change the\nordinary molecule sheet response or any frontend UI.",
        "operationId": "listGroupScoreObservations",
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Group Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicScoreObservationQuery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicScoreObservationPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/molecules/schemas": {
      "get": {
        "tags": [
          "molecules-public"
        ],
        "summary": "List Schemas",
        "description": "List your schemas.\n\nPass `scope=org` to include every schema in your organization.",
        "operationId": "listSchemas",
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(mine|org)$",
              "default": "mine",
              "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 for the first page; follow `nextCursor` until null (an empty `items` page while `nextCursor` is set is normal).",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit for the first page; follow `nextCursor` until null (an empty `items` page while `nextCursor` is set is normal)."
          }
        ],
        "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\nScalar fields (`string`, `integer`, `float`, `boolean`, `category`) constrain a molecule's metadata.\n\nA `chain` field describes a required chain, named by its `name`.",
        "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\nSending `fields` replaces the whole list.\n\nMolecules already published to bound groups are left as they are. Imports that have not\npublished yet are validated against the current schema at publication.",
        "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.\n\nThis detaches group membership; it does not delete the molecule, which stays in any other groups with its scores and files intact. To delete a molecule everywhere, use `DELETE /molecules/{moleculeId}`.\n\nIdempotent — ids not in the group are ignored, and `removedIds` lists what was detached.",
        "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": "Queue molecules for JSON upload ingestion.\n\nReturns HTTP 202 with the exact content-addressed molecule ids and an import id.\nPoll `GET /molecules/imports/{importId}` until the import reaches `ingested`,\n`failed`, or `cancelled`; the ids become readable only after ingestion succeeds.",
        "operationId": "uploadMolecules",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicUploadRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Successful Response",
            "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": "Import molecules from a file. Step 1 of 3.\n\nThen `PUT` the bytes to `uploadUrl`, and `POST\n/molecules/imports/{importId}/commit` to enqueue ingestion.\n\nFor a CSV, describe your chain columns with `chainMapping` (keyed by chain id);\n`columnMapping` handles the scalar columns only. For `pdb`/`sdf`/`fasta`/`zip`\nthe 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": "Poll an import's progress after upload or commit.\n\nStatus moves `created` → `uploaded` → `queued` → `ingested` (or `failed` / `cancelled`).\n\nIt 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"
          }
        }
      },
      "delete": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Cancel or delete an import",
        "description": "Cancel an unclaimed import and clean its source objects.",
        "operationId": "deleteImport",
        "parameters": [
          {
            "name": "import_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Import 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"
          }
        }
      }
    },
    "/api/molecules/imports/{import_id}/commit": {
      "post": {
        "tags": [
          "molecules-public"
        ],
        "summary": "Commit an import",
        "description": "Step 3 of 3: enqueue ingestion for the uploaded file.\n\n- Returns `status: \"queued\"` immediately by default — poll `GET /molecules/imports/{importId}`.",
        "operationId": "commitImport",
        "parameters": [
          {
            "name": "import_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Import Id"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/PublicCommitRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Body"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "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\nEach molecule comes back with its chains, scores, metadata, notes, tags and files inline. By\ndefault you see molecules in groups you created; pass `scope=org` for your whole organization.\n\nNarrow the list three ways, and they combine:\n\n- Source — `group` for one group, `jobId` or `jobName` for one job's inputs and outputs, `nodeRunId` for one pipeline step's results.\n- Search — `search` matches molecule names, metadata keys and score metric names. `mode=sequence` matches a sequence in any chain except a small molecule's instead, and is the one narrowing that does not combine with `tags`.\n- Filter — `filter=ptm:gte:0.8` matches that score name from any tool or a `ptm` property; `filter=alphafold.ptm:gte:0.8` selects one tool, and `filter=prop@ptm:gte:0.8` selects only the property. Send several and a molecule must pass every condition: repeat the key, `?filter=ptm:gte:0.8&filter=stage:eq:lead`, or pass a list, `params={\"filter\": [\"ptm:gte:0.8\", \"stage:eq:lead\"]}`. Bare and `tool.metric` fields each count toward the limit of 4 score conditions; a `prop@` field does not. `tags=lead` keeps the molecules carrying that tag; repeat it to require several.\n\nSort with `sortBy` and `dir` — a built-in column, a bare property-or-score name, or\na `tool.metric` score. See `GET /tools` for the scores each tool reports.\n\nPaginated — 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 you did not create returns an empty page unless you also pass `scope=org`.",
              "title": "Group"
            },
            "description": "Limit to one group, by id. A group you did not create returns an empty page unless you also pass `scope=org`."
          },
          {
            "name": "jobId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit to one job's molecules, its inputs and its outputs.",
              "title": "Jobid"
            },
            "description": "Limit to one job's molecules, its inputs and its outputs."
          },
          {
            "name": "jobName",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Same as `jobId`, by name. Names are not unique, so every job you can see with that name counts.",
              "title": "Jobname"
            },
            "description": "Same as `jobId`, by name. Names are not unique, so every job you can see with that name counts."
          },
          {
            "name": "nodeRunId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit to one pipeline step's results — a `nodeRuns[].id` from `GET /pipelines/runs/{run_id}`, or the `nr-`-prefixed `runId` on that step's score entries. `scope` defaults to `org` here unless you pass one. This narrows which molecules you get, not which scores they carry: `filter` and `sortBy` still match and rank a tool's scores across all of its runs, and a molecule scored by several runs returns all of them, up to the inline cap that sets `truncated`, so read the entry whose `runId` matches. While the step is still producing, a walk can miss molecules that arrive mid-way, and one sorted any way but the default or `groupName` can also skip or repeat them. Molecules that are in none of your groups are not listed here; `GET /pipelines/runs/{run_id}/node-runs/{node_run_id}/molecules` is scoped to the run instead and still shows them. A node run from outside your organization returns an empty page.",
              "title": "Noderunid"
            },
            "description": "Limit to one pipeline step's results — a `nodeRuns[].id` from `GET /pipelines/runs/{run_id}`, or the `nr-`-prefixed `runId` on that step's score entries. `scope` defaults to `org` here unless you pass one. This narrows which molecules you get, not which scores they carry: `filter` and `sortBy` still match and rank a tool's scores across all of its runs, and a molecule scored by several runs returns all of them, up to the inline cap that sets `truncated`, so read the entry whose `runId` matches. While the step is still producing, a walk can miss molecules that arrive mid-way, and one sorted any way but the default or `groupName` can also skip or repeat them. Molecules that are in none of your groups are not listed here; `GET /pipelines/runs/{run_id}/node-runs/{node_run_id}/molecules` is scoped to the run instead and still shows them. A node run from outside your organization returns an empty page."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MoleculeType"
                  }
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit to one or more molecule kinds: `protein`, `antibody`, `peptide`, `enzyme`, `small_molecule`, `nucleic_acid` or `small_molecule_binding_protein`. Repeat it to see several at once.",
              "title": "Type"
            },
            "description": "Limit to one or more molecule kinds: `protein`, `antibody`, `peptide`, `enzyme`, `small_molecule`, `nucleic_acid` or `small_molecule_binding_protein`. Repeat it to see several at once."
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 10000
                },
                {
                  "type": "null"
                }
              ],
              "description": "What to look for. Matches molecule names by substring, and metadata keys and score metric names exactly — it does not search metadata values, notes or tags. Needs at least 3 characters; in `mode=default`, passing `group` lifts that floor. Add `mode=sequence` to search sequences instead, in any chain except a small molecule's.",
              "title": "Search"
            },
            "description": "What to look for. Matches molecule names by substring, and metadata keys and score metric names exactly — it does not search metadata values, notes or tags. Needs at least 3 characters; in `mode=default`, passing `group` lifts that floor. Add `mode=sequence` to search sequences instead, in any chain except a small molecule's."
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(default|name|sequence)$",
              "description": "How `search` is read. `default` (alias `name`) matches molecule names, metadata keys and score metric names; `sequence` matches a sequence in any chain except a small molecule's. Follow `nextCursor` until it is null. In `default` mode the server keeps looking within one request until it has a full page, so a page is short or empty with a cursor still set only when the scan stopped early: its time or chunk budget ran out, or a query partway through timed out or could not get a database connection. That cursor picks up where the scan stopped; `sequence` returns as soon as it has any match. `filter` combines with `mode=sequence`: its conditions narrow the molecules the sequence scan finds and the scan still covers every chain, so keep following `nextCursor` until it is null. The `sequence` parameter, `tags`, `moleculesPerGroup` (or `examplesPerGroup`), any `sortBy` other than `added` or `groupName`, and `dir=desc` are still refused in that mode. Combining `search` with `sequence` caps the name half per request rather than per walk, so following `nextCursor` to the end still reaches every possible name match. The response echoes the `mode` it used.",
              "default": "default",
              "title": "Mode"
            },
            "description": "How `search` is read. `default` (alias `name`) matches molecule names, metadata keys and score metric names; `sequence` matches a sequence in any chain except a small molecule's. Follow `nextCursor` until it is null. In `default` mode the server keeps looking within one request until it has a full page, so a page is short or empty with a cursor still set only when the scan stopped early: its time or chunk budget ran out, or a query partway through timed out or could not get a database connection. That cursor picks up where the scan stopped; `sequence` returns as soon as it has any match. `filter` combines with `mode=sequence`: its conditions narrow the molecules the sequence scan finds and the scan still covers every chain, so keep following `nextCursor` until it is null. The `sequence` parameter, `tags`, `moleculesPerGroup` (or `examplesPerGroup`), any `sortBy` other than `added` or `groupName`, and `dir=desc` are still refused in that mode. Combining `search` with `sequence` caps the name half per request rather than per walk, so following `nextCursor` to the end still reaches every possible name match. The response echoes the `mode` it used."
          },
          {
            "name": "sequenceMatch",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(subsequence|exact)$",
              "description": "With `mode=sequence`, whether a chain only needs to contain the term (`subsequence`, the default) or must equal it (`exact`). Rejected in name mode.",
              "default": "subsequence",
              "title": "Sequencematch"
            },
            "description": "With `mode=sequence`, whether a chain only needs to contain the term (`subsequence`, the default) or must equal it (`exact`). Rejected in name mode."
          },
          {
            "name": "moleculesPerGroup",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 25,
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Show at most this many molecules from each group, so one large group does not fill the results and you see a few molecules from many groups instead. The cap covers the whole listing rather than each page: a group contributes at most this many molecules however far you walk, though its share can still straddle a page boundary. Leave it out and there is no cap; to see all of one group's molecules, send that `group` without it. Needs `sortBy=groupName`. Not allowed with `mode=sequence`.",
              "title": "Moleculespergroup"
            },
            "description": "Show at most this many molecules from each group, so one large group does not fill the results and you see a few molecules from many groups instead. The cap covers the whole listing rather than each page: a group contributes at most this many molecules however far you walk, though its share can still straddle a page boundary. Leave it out and there is no cap; to see all of one group's molecules, send that `group` without it. Needs `sortBy=groupName`. Not allowed with `mode=sequence`."
          },
          {
            "name": "examplesPerGroup",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 25,
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Deprecated: the old name for `moleculesPerGroup`, still accepted so existing callers keep working. Send one or the other; sending both with different values is a 422.",
              "deprecated": true,
              "title": "Examplespergroup"
            },
            "description": "Deprecated: the old name for `moleculesPerGroup`, still accepted so existing callers keep working. Send one or the other; sending both with different values is a 422.",
            "deprecated": true
          },
          {
            "name": "sequence",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Match molecules containing this subsequence in any chain except a small molecule's — their SMILES share the column and the alphabet, so they are excluded; nucleic acids are not. Not case-sensitive, at least 3 characters.",
              "title": "Sequence"
            },
            "description": "Match molecules containing this subsequence in any chain except a small molecule's — their SMILES share the column and the alphabet, so they are excluded; nucleic acids are not. Not case-sensitive, at least 3 characters."
          },
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "maxItems": 32
                },
                {
                  "type": "null"
                }
              ],
              "description": "One condition, written `field:operator:value`. Send a single value or an array, and a molecule must pass every condition: repeat the key in the URL (`?filter=alphafold.ptm:gte:0.8&filter=stage:eq:lead`), or pass a list in a params dict (`params={\"filter\": [\"alphafold.ptm:gte:0.8\", \"stage:eq:lead\"]}`). A bare field (`ptm`) matches either a metadata property or that literal score name from any tool, so it counts as a score condition. Write `tool.metric` (`alphafold.ptm`) to select one tool, or `prop@field` (`prop@ptm`) to select only the property; a `prop@` field never counts as a score condition. `prop@` also reads the key verbatim, dots and surrounding spaces included, so it reaches a metadata key such as `IC50.nM` (`prop@IC50.nM:gte:5`) that would otherwise be read as `tool.metric`. See `GET /tools` for the scores each tool reports. Operators: `gt`, `gte`, `lt`, `lte` for numbers; `eq`, `neq` for equality, case-sensitive and matching a number against its stored text (`1` finds a stored `1.0`); `iequals` for the same ignoring case; `includes` for text containing the value, also ignoring case; and `exists`, written `field:exists` with no value, for a field that is present whatever it holds. Append `:jobId` to a `tool.metric` condition to pin it to one job: `alphafold.ptm:gte:0.9:<jobId>`. On a bare or `prop@` field the suffix is read as part of the value. At most 4 score conditions per request, bare and `tool.metric` fields alike, and 32 conditions in total.",
              "title": "Filter"
            },
            "description": "One condition, written `field:operator:value`. Send a single value or an array, and a molecule must pass every condition: repeat the key in the URL (`?filter=alphafold.ptm:gte:0.8&filter=stage:eq:lead`), or pass a list in a params dict (`params={\"filter\": [\"alphafold.ptm:gte:0.8\", \"stage:eq:lead\"]}`). A bare field (`ptm`) matches either a metadata property or that literal score name from any tool, so it counts as a score condition. Write `tool.metric` (`alphafold.ptm`) to select one tool, or `prop@field` (`prop@ptm`) to select only the property; a `prop@` field never counts as a score condition. `prop@` also reads the key verbatim, dots and surrounding spaces included, so it reaches a metadata key such as `IC50.nM` (`prop@IC50.nM:gte:5`) that would otherwise be read as `tool.metric`. See `GET /tools` for the scores each tool reports. Operators: `gt`, `gte`, `lt`, `lte` for numbers; `eq`, `neq` for equality, case-sensitive and matching a number against its stored text (`1` finds a stored `1.0`); `iequals` for the same ignoring case; `includes` for text containing the value, also ignoring case; and `exists`, written `field:exists` with no value, for a field that is present whatever it holds. Append `:jobId` to a `tool.metric` condition to pin it to one job: `alphafold.ptm:gte:0.9:<jobId>`. On a bare or `prop@` field the suffix is read as part of the value. At most 4 score conditions per request, bare and `tool.metric` fields alike, and 32 conditions in total."
          },
          {
            "name": "tags",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "maxItems": 32
                },
                {
                  "type": "null"
                }
              ],
              "description": "A tag the molecule must carry. Repeat it and a molecule must carry every tag given (`?tags=lead&tags=reviewed`). Matched against the same list each item returns as `tags`, whole and case-sensitive: no substring, no case folding. This is not `filter`: `filter=tags:eq:lead` reads a metadata property or score named `tags`, which is different data. At most 32 tags. Not allowed with `mode=sequence`.",
              "title": "Tags"
            },
            "description": "A tag the molecule must carry. Repeat it and a molecule must carry every tag given (`?tags=lead&tags=reviewed`). Matched against the same list each item returns as `tags`, whole and case-sensitive: no substring, no case folding. This is not `filter`: `filter=tags:eq:lead` reads a metadata property or score named `tags`, which is different data. At most 32 tags. Not allowed with `mode=sequence`."
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(mine|org)$",
              "description": "`mine` (the default) shows molecules in groups you created; `org` shows everything across your organization. `org` needs your organization to share molecules between members, or you to be an org admin; without that it behaves as `mine`.",
              "default": "mine",
              "title": "Scope"
            },
            "description": "`mine` (the default) shows molecules in groups you created; `org` shows everything across your organization. `org` needs your organization to share molecules between members, or you to be an org admin; without that it behaves as `mine`."
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Sort by a built-in column (`id`, `added`, `type`, `groupName`), a bare name, or a tool score (`tool.metric`, on the tool's best run). A bare name ranks on whichever value ranks first among a metadata property of that name and that literal score name from any tool; `prop@` is not recognized here. Newest created first by default. `groupName` keeps a group's molecules together — pair it with `moleculesPerGroup`.",
              "default": "added",
              "title": "Sortby"
            },
            "description": "Sort by a built-in column (`id`, `added`, `type`, `groupName`), a bare name, or a tool score (`tool.metric`, on the tool's best run). A bare name ranks on whichever value ranks first among a metadata property of that name and that literal score name from any tool; `prop@` is not recognized here. Newest created first by default. `groupName` keeps a group's molecules together — pair it with `moleculesPerGroup`."
          },
          {
            "name": "dir",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(asc|desc)$",
              "description": "Sort direction, `asc` or `desc`. With `mode=sequence`, only `asc`.",
              "default": "desc",
              "title": "Dir"
            },
            "description": "Sort direction, `asc` or `desc`. With `mode=sequence`, only `asc`."
          },
          {
            "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 for the first page, and follow `nextCursor` until it is null. A page can still come back short or empty with a cursor set when the scan stops early, because its time or chunk budget ran out or a query partway through timed out or could not get a database connection; the cursor continues from where the scan stopped.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit for the first page, and follow `nextCursor` until it is null. A page can still come back short or empty with a cursor set when the scan stops early, because its time or chunk budget ran out or a query partway through timed out or could not get a database connection; the cursor continues from where the scan stopped."
          }
        ],
        "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\nMerge your own annotations with `properties` (tool scores aren't editable here). Set the derived-from molecule with `source`, this group's primary structure file with `fileId`, and the per-group display name with `name`.\n\nA `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.\n\nUse this when `GET /molecules/{moleculeId}` marks `groups` as truncated. Only your own groups are listed.\n\nPaginated — 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 for the first page; follow `nextCursor` until null (an empty `items` page while `nextCursor` is set is normal).",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`. Omit for the first page; follow `nextCursor` until null (an empty `items` page while `nextCursor` is set is normal)."
          }
        ],
        "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\nAddressed by its id; no group needed.\n\nChain labels are per-group, so 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.\n\nRemoves the molecule and all its group memberships, scores, and file links across the organization. To remove it from one group instead, use `POST /molecules/remove`.\n\nIdempotent — 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 in your account or organization.",
        "operationId": "listTemplates",
        "parameters": [
          {
            "name": "isPublished",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter to published (`true`) or unpublished (`false`) templates.",
              "title": "Ispublished"
            },
            "description": "Filter to published (`true`) or unpublished (`false`) templates."
          },
          {
            "name": "owner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "mine",
                    "org"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Whose templates to list: `mine` (the default) or `org` for your whole organization.",
              "title": "Owner"
            },
            "description": "Whose templates to list: `mine` (the default) or `org` for your whole organization."
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Search pipelines by name.",
              "title": "Search"
            },
            "description": "Search pipelines by name."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "description": "Maximum number of templates to return in one page.",
              "default": 50,
              "title": "Limit"
            },
            "description": "Maximum number of templates to return in one page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "description": "Pagination token from the previous response's `nextCursor`.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`."
          }
        ],
        "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": "Retrieve a template by id to view its nodes and required inputs.",
        "operationId": "getTemplate",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          },
          {
            "name": "version",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "A specific version to view (a `vN` handle); omit for the current version.",
              "title": "Version"
            },
            "description": "A specific version to view (a `vN` handle); omit for the current version."
          }
        ],
        "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": "Archive a pipeline template",
        "description": "Archive a template. Its versions and existing runs remain available historically.",
        "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}/publish": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Publish a template version",
        "description": "Publish a version of your pipeline template to your organization.\n\nOthers in your organization may only run pipelines published to the organization\n\nOnly 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": "Create a copy of a version of your existing pipeline (latest by default).",
        "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}/validate": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Validate a template",
        "description": "Validate the settings and inputs of a template without creating or executing it.\n\nValidates your pipeline to ensure compatible connections, required tool settings, and valid graph structure.",
        "operationId": "validateTemplate",
        "parameters": [
          {
            "name": "template_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Template Id"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/PublicValidateTemplateRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Body"
              }
            }
          }
        },
        "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/submit": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Submit a pipeline run",
        "description": "Submit a pipeline run from an existing template, or from a new pipeline graph.\n\nProvide a `bindings` map, which defines the molecules/files you want to use for each input node in your pipeline. If your input contains chains, define the mapping of chain IDs between your molecule and the pipeline's reference/default (if running against an existing template).\n\nBindings may be defined as an array of sequences, smiles, or sdf/pdb files (uploaded using the /upload endpoint), or an existing molecule group id (created using /molecules/groups below).\n\nIf applicable, put residue selections inside each binding's `residuesByChain` for the tools that require hotspots / designed residues. Two shapes, auto-detected by the value: SIMPLE `{chain: ranges}` (a chain maps to a range string) fans one selection to every residue field the input feeds; ADVANCED `{toolNodeId: {field: {chain: ranges}}}` (a node maps to a per-field object) picks per (tool node, field). An inline submit is stamped Simplified/Advanced from the shape you send; picks under each binding are merged. Chain and residue selections can also be supplied in tool-node `settings` or top-level per-node `settings`. Reference chain labels follow `chainMapping`. Conflicting inline and binding residue selections are rejected.\n\nChoose from your pipelines or example templates to view example scripts.",
        "operationId": "submitPipeline",
        "parameters": [
          {
            "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/PublicSubmitPipelineRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRun"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "x-doc-group": "Pipelines"
      }
    },
    "/api/pipelines/validate": {
      "post": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Validate a pipeline run",
        "description": "Validate the settings and inputs of a run without creating or executing it. Uses the same settings as `/submit`.\n\nValidates your pipeline to ensure compatible connections, required tool settings, and valid graph structure.",
        "operationId": "validateRun",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicSubmitPipelineRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicValidateResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "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.",
        "operationId": "listRuns",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/RunStatus"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by run status.",
              "title": "Status"
            },
            "description": "Filter by run status."
          },
          {
            "name": "templateId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter to runs of one pipeline (by template id).",
              "title": "Templateid"
            },
            "description": "Filter to runs of one pipeline (by template id)."
          },
          {
            "name": "owner",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "mine",
                    "org"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Whose runs to list: `mine` (the default) or `org` for your whole organization.",
              "title": "Owner"
            },
            "description": "Whose runs to list: `mine` (the default) or `org` for your whole organization."
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "test",
                    "production"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by run source: `test` or `production`.",
              "title": "Source"
            },
            "description": "Filter by run source: `test` or `production`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "description": "Maximum number of runs to return in one page.",
              "default": 50,
              "title": "Limit"
            },
            "description": "Maximum number of runs to return in one page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "description": "Pagination token from the previous response's `nextCursor`.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`."
          }
        ],
        "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/results": {
      "get": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "Download a pipeline run's results",
        "description": "Download full raw results for pipeline by its job name",
        "operationId": "getRunResults",
        "parameters": [
          {
            "name": "jobName",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The run's job name.",
              "title": "Jobname"
            },
            "description": "The run's job name."
          },
          {
            "name": "user",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 320
                },
                {
                  "type": "null"
                }
              ],
              "description": "The run owner's email — needed only to disambiguate a job name shared across accounts.",
              "title": "User"
            },
            "description": "The run owner's email — needed only to disambiguate a job name shared across accounts."
          },
          {
            "name": "nodeRunId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Scope the ZIP to one node run — a `nodeRuns[].id` from `GET /runs/{id}`.",
              "title": "Noderunid"
            },
            "description": "Scope the ZIP to one node run — a `nodeRuns[].id` from `GET /runs/{id}`."
          },
          {
            "name": "runId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "The run's id, needed only to disambiguate a job name you have reused across your own runs. It narrows the runs you can already see; a run id you cannot view is not found.",
              "title": "Runid"
            },
            "description": "The run's id, needed only to disambiguate a job name you have reused across your own runs. It narrows the runs you can already see; a run id you cannot view is not found."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicRunResults"
                }
              }
            }
          },
          "202": {
            "description": "The archive is still being built — GET again to keep waiting."
          },
          "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": "Query a run for its status overall and per-node, along with output results.",
        "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}/node-runs/{node_run_id}/molecules": {
      "get": {
        "tags": [
          "pipelines-public"
        ],
        "summary": "List the molecules a node run produced",
        "description": "The molecules one node run produced, with their scores.\n\nRead a node run's results through this, not through `PublicNodeRun.outputGroup`. `outputGroup` is the group a node MINTED, and two common kinds of node mint none: a node that enriches its inputs in place (scoring, structure prediction) leaves its molecules in the group they came from, and a filter node's survivors exist only as node-run outputs. For both, `outputGroup` is correctly `null` — reading results by group reports 'produced nothing' for exactly the node runs whose output you asked for. This endpoint answers for every kind of node.\n\n`node_run_id` is the `id` of an entry in the run's `nodeRuns`. `limit` is a maximum and a page may be shorter when rows are large; follow `nextCursor`.\n\nThis is the COMPACT view: identity, sequence and scores, scoped to the RUN. For the same molecules with their groups, tags and provenance — and to threshold or rank them by a score — pass the same id to `GET /molecules?nodeRunId=...`, which supports `filter`, `sortBy`, `search` and 200 per page. That endpoint reads through your molecule library rather than through the run, so its `nodeRunId` parameter documents where the two differ; this one is the authority on what the node run produced.",
        "operationId": "listNodeRunMolecules",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Run Id"
            }
          },
          {
            "name": "node_run_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Node Run Id"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 500,
              "minimum": 1,
              "description": "Molecules per page.",
              "default": 50,
              "title": "Limit"
            },
            "description": "Molecules per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 4096
                },
                {
                  "type": "null"
                }
              ],
              "description": "Pagination token from the previous response's `nextCursor`.",
              "title": "Cursor"
            },
            "description": "Pagination token from the previous response's `nextCursor`."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicNodeRunMoleculePage"
                }
              }
            }
          },
          "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 a pipeline run.\n\nAny jobs which have not yet completed are stopped, including running jobs.\nOutputs of any completed jobs are saved and may be viewed.",
        "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"
      }
    },
    "/api/jobs": {
      "get": {
        "tags": [
          "jobs-public"
        ],
        "summary": "List jobs",
        "description": "List jobs using the established `/api/jobs` request and response contract, including exact-name, batch-child, organization, member, subjob, batch-only, and pagination modes.",
        "operationId": "listCompatibleJobs",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/LegacyJobPage"
                    },
                    {
                      "$ref": "#/components/schemas/LegacySingleJob"
                    },
                    {
                      "$ref": "#/components/schemas/LegacyMultiJob"
                    }
                  ],
                  "title": "Response Listcompatiblejobs"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1000,
              "minimum": 1,
              "maximum": 1000
            }
          },
          {
            "in": "query",
            "name": "startKey",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          {
            "in": "query",
            "name": "jobName",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          {
            "in": "query",
            "name": "includeSequences",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "in": "query",
            "name": "includeSubjobs",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "in": "query",
            "name": "organization",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "in": "query",
            "name": "batchOnly",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "in": "query",
            "name": "batch",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          {
            "in": "query",
            "name": "jobEmail",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            }
          }
        ]
      }
    },
    "/api/jobs/search": {
      "post": {
        "tags": [
          "jobs-public"
        ],
        "summary": "Look up many jobs by exact name",
        "description": "Resolve up to 1000 exact job names in ONE request and get each job's status back, plus the names that matched nothing in `notFound`. Exactly one row per resolved name, the newest — under `organization=true` a name can match once per member, and `GET /jobs?jobName=` is where every match is available. This is the batched form of `GET /jobs?jobName=`: polling or existence-checking a large campaign one name per request is a request volume that gets rate-limited and then blocked at the network edge. Names are sent in the body because a query string cannot hold a useful number of them. Rows carry status and timing but NOT `Settings`, `Score` or `AggregationError` — the three fields with no bound on their size, whose absence is what makes the response size a function of the name cap. Read those for one job with `GET /jobs?jobName=`. To enumerate one batch's children instead of naming them, page `GET /jobs?batch=` with `limit` and `startKey`.",
        "operationId": "searchJobsByName",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LegacyJobNamesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyJobNamesPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "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": {
      "ClassicPublicProblem": {
        "additionalProperties": false,
        "description": "RFC 9457 problem detail. Serialized as `application/problem+json` on every public error.",
        "properties": {
          "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"
          },
          "status": {
            "description": "The HTTP status code, duplicated in the body per RFC 9457.",
            "title": "Status",
            "type": "integer"
          },
          "title": {
            "description": "A short, human-readable summary of the error kind.",
            "title": "Title",
            "type": "string"
          },
          "type": {
            "description": "A URI identifying the error kind (dereferenceable docs).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "title": "PublicProblem",
        "type": "object"
      },
      "PublicToolRecommendationRequest": {
        "additionalProperties": false,
        "description": "Ask which tool to run for a stated task.",
        "properties": {
          "inputs": {
            "description": "Your input sequence components, to tailor the recommendation to your specific inputs. Worth setting: it is what makes a request antibody-antigen rather than a guess, and it sharpens the benchmark match.",
            "items": {
              "$ref": "#/components/schemas/PublicRecommendationInput"
            },
            "maxItems": 32,
            "title": "Inputs",
            "type": "array"
          },
          "prompt": {
            "description": "What you want to do, in your own words — e.g. 'predict the structure of this antibody-antigen complex'.",
            "maxLength": 20000,
            "minLength": 1,
            "title": "Prompt",
            "type": "string"
          }
        },
        "required": [
          "prompt"
        ],
        "title": "PublicToolRecommendationRequest",
        "type": "object"
      },
      "PublicRecommendationInput": {
        "additionalProperties": false,
        "description": "One thing you already have. Give exactly one of `sequence`, `smiles`, or `ccd`.\n\nA protein chain is `sequence`. A small molecule is `smiles` or `ccd`, never both.",
        "properties": {
          "ccd": {
            "anyOf": [
              {
                "maxLength": 8,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "A ligand, as a PDB component id.",
            "title": "Ccd"
          },
          "id": {
            "anyOf": [
              {
                "maxLength": 200,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional label, used to point at this input in error messages.",
            "title": "Id"
          },
          "role": {
            "anyOf": [
              {
                "maxLength": 40,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Its part in the complex — `heavy`, `light`, `antigen`, `target` or `enzyme`. Optional, and worth setting: it is what makes a request antibody-antigen rather than a guess, and it sharpens the benchmark match.",
            "title": "Role"
          },
          "sequence": {
            "anyOf": [
              {
                "maxLength": 10000,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "One protein chain, as amino-acid residues.",
            "title": "Sequence"
          },
          "smiles": {
            "anyOf": [
              {
                "maxLength": 10000,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "A ligand, as SMILES.",
            "title": "Smiles"
          }
        },
        "title": "PublicRecommendationInput",
        "type": "object"
      },
      "PublicRecommendedTool": {
        "description": "One tool, in recommended order. The ORDER is the recommendation.",
        "properties": {
          "reason": {
            "description": "Why this tool, in prose.",
            "title": "Reason",
            "type": "string"
          },
          "toolId": {
            "description": "The tool to run, as named by the tool catalogue.",
            "title": "Toolid",
            "type": "string"
          }
        },
        "required": [
          "toolId",
          "reason"
        ],
        "title": "PublicRecommendedTool",
        "type": "object"
      },
      "PublicToolRecommendation": {
        "description": "A ranked recommendation, or a reasoned refusal to make one.\n\n`abstain` is a normal outcome, not an error: it means no tool could be defended\nfor this request, and `summary` says why. Callers should handle it as an answer.",
        "properties": {
          "abstain": {
            "description": "`false` — `tools` is ranked, best first, and the first entry is what to run. `true` — no tool could be justified, `tools` is empty, and `summary` says what was missing. An abstention is an answer, not an error.",
            "title": "Abstain",
            "type": "boolean"
          },
          "summary": {
            "description": "A short explanation of the ranking, for a person to read. It also carries the caveats: whether the benchmark had measured your target, whether anything measured this kind of task at all, and what the service could not establish.",
            "title": "Summary",
            "type": "string"
          },
          "tools": {
            "description": "Ranked best-first, and only the tools worth running — usually two to four, not the whole catalogue. Empty when `abstain` is true.",
            "items": {
              "$ref": "#/components/schemas/PublicRecommendedTool"
            },
            "title": "Tools",
            "type": "array"
          }
        },
        "required": [
          "abstain",
          "summary",
          "tools"
        ],
        "title": "PublicToolRecommendation",
        "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"
            }
          },
          "projectTag": {
            "$ref": "#/components/schemas/ProjectTag"
          }
        }
      },
      "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. A batch name that is empty after normalization is rejected with 400.",
            "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}`.\nNo stored name may equal the batch's own name. The batch is itself a job under that name, so the two would collide and the batch could never finish; such a submission is rejected with 400.",
            "minItems": 1,
            "maxItems": 30000,
            "items": {
              "$ref": "#/components/schemas/JobSubmission/properties/jobName"
            }
          },
          "projectTag": {
            "$ref": "#/components/schemas/ProjectTag"
          }
        }
      },
      "ProjectTag": {
        "type": "string",
        "description": "An organization project to tag the job(s) with — its project id, or its name. Applies to the whole batch on `/submit-batch`, and to every job a pipeline creates on `/submit-pipeline`.\nUsually optional. It is REQUIRED when your organization has turned on \"require a project tag\": those accounts get 400 on any submission that does not resolve to an ORGANIZATION project (a personal project does not satisfy the policy). That 400 body carries `availableProjects` — the projects you may use — so you can retry without looking them up elsewhere.",
        "example": "8f1c2b64-9a3e-4c77-b0d1-4a2f6e5d8c90"
      },
      "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"
          }
        }
      },
      "Project": {
        "type": "object",
        "properties": {
          "ProjectId": {
            "type": "string",
            "description": "The id to send as `projectTag`. Always accepted. `ProjectName` is accepted TOO, but only for `org` and `personal` scopes — a `shared` project is resolvable by id ONLY, because shared projects live in different creators' partitions where name uniqueness is not enforced, so a name could route the submission to the wrong project. Send the id and it always works."
          },
          "ProjectName": {
            "type": "string"
          },
          "Organization": {
            "type": "string",
            "description": "The scope that owns it — your organization's slug, or `PERSONAL#<email>`."
          },
          "Description": {
            "type": "string"
          },
          "Color": {
            "type": "string"
          },
          "CreatedBy": {
            "type": "string",
            "format": "email"
          },
          "Created": {
            "type": "string",
            "format": "date-time",
            "description": "ISO-8601. The field is `Created`, not `CreatedAt`."
          },
          "Updated": {
            "type": "string",
            "format": "date-time"
          },
          "scope": {
            "type": "string",
            "enum": [
              "org",
              "personal",
              "shared"
            ],
            "description": "Which query found it, and ONLY `org` satisfies an organization's required-project policy — `personal` is your own, `shared` is someone else's private project you were invited to.\nRead it together with `isInOrg`. When `isInOrg` is false you have no organization, so your personal partition IS your default scope and every project comes back `org`; there is also no required-project policy to satisfy. Branch on `isInOrg` first, not on `scope` alone."
          },
          "jobCount": {
            "type": "integer",
            "description": "Present only when `includeCounts=true`."
          }
        }
      },
      "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"
          },
          "getApiKey": {
            "type": "string",
            "format": "uri",
            "description": "Present on a missing/invalid API-key rejection: the page where a key is created, on the host that served this response (a tenant deployment names its own host, because a key from the shared app does not work against a tenant pod). Agents arrive by calling and failing rather than by browsing, so the way out ships with the rejection."
          },
          "agentGuide": {
            "type": "string",
            "format": "uri",
            "description": "Present on a missing/invalid API-key rejection: the LLM-readable API guide."
          },
          "toolCatalog": {
            "type": "string",
            "format": "uri",
            "description": "Present on a missing/invalid API-key rejection: the keyless tool catalog, so a correct payload can be built before a key exists."
          },
          "hint": {
            "type": "string",
            "description": "Plain-language next step, present on the rejections that have one. On a missing/invalid API-key rejection it states that a key cannot be minted programmatically; on a required-project rejection it says to retry with `projectTag` set to one of `availableProjects`."
          },
          "availableProjects": {
            "type": "array",
            "description": "OPTIONAL, on a required-project rejection (see `ProjectTag`): organization projects this caller may tag a submission with, sent WITH the refusal so a retry needs no second call. At most 25.\nDo not depend on it. The lookup is bounded and fails open, so the field is ABSENT whenever it found nothing — and its absence says nothing about whether projects exist. `hint` is always present on this rejection and points at `GET /projects/list`, which is the complete answer.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Pass this as `projectTag`."
                },
                "name": {
                  "type": "string",
                  "description": "The project's display name. Also accepted as `projectTag`."
                }
              }
            }
          }
        }
      },
      "PublicToolInfo": {
        "type": "object",
        "description": "One tool in the keyless catalogue (`/tools-catalog`). Note the field names differ from `ToolInfo` on purpose: the wire value is `type` here, matching the key you actually send to `/submit-job`, while the human label is `displayName`. (`ToolInfo` calls the same wire value `name`.) A display name is never a valid `type`.",
        "properties": {
          "type": {
            "type": "string",
            "description": "The exact, case-sensitive value to send as `type` when submitting.",
            "example": "alphafold"
          },
          "displayName": {
            "type": "string",
            "description": "Human label for the tool. NOT a valid `type` value.",
            "example": "AlphaFold"
          },
          "description": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "description": "Intent tags, usable as the `tag` query parameter.",
            "items": {
              "type": "string"
            },
            "example": [
              "structure-prediction"
            ]
          },
          "taskSetting": {
            "type": "string",
            "description": "The setting whose value the `tasks` predicates below refer to. Present only for multi-task tools. Do NOT assume it is called `task`: it is `metricType` on `protein-metrics`, `binderType` on `boltzgen` and `inputFormat` on `boltz`, `chai` and `esmfold2`, among 55 tools that name it something else. Sending the wrong key produces no error — unrecognised settings keys are flagged, not rejected — just the wrong set of required fields.",
            "example": "metricType"
          },
          "requiredSettings": {
            "type": "array",
            "description": "The tool's REQUIRED settings only — enough to build a valid payload without a key. Optional parameters, defaults and descriptions need `/tools/{name}/schema`. Send these names verbatim: an unrecognised key is flagged, not rejected, so a typo shows up only as the real field reading as missing.\n\nRequiredness is usually CONDITIONAL, so read `tasks` and `conditionals` before treating this as a checklist: most multi-task tools list alternative branches, not fields that are all needed at once. `proto`, for example, requires `task` plus exactly ONE of the remaining entries, and `rfdiffusion3` lists `jsonFile` and `jsonConfig`, which are mutually exclusive.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Send this exact key inside `settings`."
                },
                "required": {
                  "type": "boolean",
                  "description": "Present and `false` on entries you need NOT send. They appear in this list only because another entry's `tasks` or `conditionals` names them, and a predicate whose controller is missing from the document cannot be acted on. Read their `default` — it is what silently selects a branch: `pysca.useMSA` defaults to true, so a payload sending only `fastaFile` takes the other branch and is rejected for a missing `sequence`. Omitted entirely (not set to `true`) on genuinely required entries."
                },
                "type": {
                  "type": "string",
                  "description": "The field's input type, e.g. `string`, `number`, `file`."
                },
                "list": {
                  "type": "boolean",
                  "description": "When true, send an ARRAY of `type`, not a single value. Omitted when false."
                },
                "lowerBound": {
                  "type": "number",
                  "description": "Enforced minimum. A smaller value is rejected."
                },
                "upperBound": {
                  "type": "number",
                  "description": "Enforced maximum. A larger value is rejected."
                },
                "maxLength": {
                  "type": "integer",
                  "description": "Maximum sequence length in residues (whitespace is not counted). Exceeding it is a 400. Published only where the validator actually applies the cap — a non-list field that is a `sequence` type or is literally named `sequence`. Limits range from 14 residues to 20,000, so check before sending a long chain."
                },
                "maxLengthNote": {
                  "type": "string",
                  "description": "Why the cap exists, where the tool explains it — usually \"this tool folds only the antibody variable domain (Fv)\". Read it before truncating to fit."
                },
                "sep": {
                  "type": "string",
                  "description": "The literal delimiter for a residue-selection field, published when the field ships no `example`. `\" \"` means space-separated (`\"1 2 3\"`); `\",\"` means comma-separated ranges (`\"1-5,7,9-11\"`). This matters more than it looks: the wrong form is NOT rejected, it is read as different residues, so the job silently runs on the wrong positions."
                },
                "contigsFormat": {
                  "type": "string",
                  "description": "Names the form a residue-selection field expects (e.g. `ranges`). Published alongside `sep` where the tool defines it."
                },
                "unknownResidue": {
                  "type": "string",
                  "enum": [
                    "X-stripped"
                  ],
                  "description": "Present on protein sequence fields only. `X` is NOT covered by `alphabet` and is NOT rejected: the validator accepts it and normalization REMOVES it before the job runs, which shifts every residue index after it. Absent on nucleotide fields, where `X` really is a 400."
                },
                "alphabet": {
                  "type": "string",
                  "description": "The accepted residue characters. An out-of-alphabet character is rejected with a 400 naming this same set — except `X` on a field carrying `unknownResidue`, which is accepted and stripped. Do NOT assume `type: \"sequence\"` means protein: `disco.dnaSequence` accepts `ATGCN` and `rna-fm.sequence` accepts `ACGU`, so a protein chain sent to either is refused. Whitespace is tolerated and may appear in the published set."
                },
                "defaultByTask": {
                  "description": "A per-task LOOKUP, not a value to send: read your branch out of it. Present instead of `default` when the tool's default varies by the task selector — publishing `{\"antibody\": [...], \"nanobody\": [...]}` as `default` would invite a consumer to submit the map where an array is expected. A map covering every selector option is autofilled whatever branch you pick, so those fields are not listed as required at all."
                },
                "singleChain": {
                  "type": "boolean",
                  "description": "Present and true when a colon-separated complex is REJECTED on this field. `:` is how every other sequence field in this API expresses a multi-chain input, so this is not inferable from `type: \"sequence\"`."
                },
                "minChains": {
                  "type": "integer",
                  "description": "Minimum colon-separated chains. Enforced before ordinary field validation, so a payload below it fails early."
                },
                "maxChains": {
                  "type": "integer",
                  "description": "Maximum colon-separated chains."
                },
                "subfields": {
                  "type": "array",
                  "description": "Present when each item is an OBJECT rather than a scalar — `type` and `list` alone cannot express that, and submitting a bare string is rejected. Each entry gives an item key, and its `options` where the key is an enum. Read those per tool instead of assuming a shared set: `chai` accepts a `glycan` molecule type, `boltz` does not, and `rf3` takes only `protein` and `ligand`.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "type": {
                        "type": "string"
                      },
                      "options": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "list": {
                        "type": "boolean"
                      },
                      "required": {
                        "type": "boolean"
                      }
                    }
                  }
                },
                "example": {
                  "description": "One filled-in value, published only for settings that carry `subfields` — where the nesting is the hard part. Not published for ordinary scalar settings, whose `type` already says what to send."
                },
                "default": {
                  "description": "The value that applies if you omit this setting. Present only where it is load-bearing — a task selector, or a field another entry's `conditionals` depend on. Omitting `rfdiffusion3.jsonInputType`, for instance, silently applies `file`, which is what decides whether `jsonFile` or `jsonConfig` becomes required."
                },
                "options": {
                  "type": "array",
                  "description": "When present, the value must be one of these.",
                  "items": {
                    "type": "string"
                  }
                },
                "extension": {
                  "type": "array",
                  "description": "For file fields, the EFFECTIVE accepted extensions — what submission actually takes, not the registry's raw list. Any list containing `pdb` is widened with `cif`, because the validator accepts a CIF for a PDB field and converts it before dispatch. Fields that opt out of that conversion keep their raw list.",
                  "items": {
                    "type": "string"
                  }
                },
                "tasks": {
                  "type": "array",
                  "description": "When present, this setting is required ONLY if the tool's task selector holds one of these values. The selector is named by the entry's `taskSetting` — it is not always `task`. Absent means unconditionally required.",
                  "items": {
                    "type": "string"
                  }
                },
                "conditionals": {
                  "type": "array",
                  "description": "When present, this setting is required ONLY while another setting holds a given value — the mechanism behind mutually exclusive inputs.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "otherSettingName": {
                        "type": "string",
                        "description": "The setting this one depends on."
                      },
                      "otherSettingValue": {
                        "description": "The value that makes this setting required."
                      },
                      "checkType": {
                        "type": "string",
                        "description": "How the values are compared, e.g. `equals`."
                      }
                    }
                  }
                }
              },
              "required": [
                "name"
              ]
            }
          }
        },
        "required": [
          "type"
        ]
      },
      "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.\n\n`taskType` is the tool's pipeline task category (`structure-prediction`, `inverse-folding`, `score`, …) — the fact pipelines chain on. Two tools connect when the molecule type one produces matches what the next consumes (a `pdb` output may also feed a `sequence` input, since sequences are read from structures).",
        "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": {},
                "extension": {
                  "type": "array",
                  "description": "For a file parameter, the file formats it accepts — the only published statement of what this tool's parser can read.",
                  "items": {
                    "type": "string"
                  },
                  "example": [
                    "pdb",
                    "cif"
                  ]
                },
                "list": {
                  "type": "boolean",
                  "description": "True when this parameter takes an ARRAY of the stated `type` rather than a single value."
                }
              }
            }
          },
          "outputTypes": {
            "type": "array",
            "description": "What the tool declares it produces — molecular (`pdb`, `sequence`, `sdf`, `smiles`) alongside file and score types (`csv`, `score`, `cif`, …). A few tools use compound values such as `pdb-list`, so match by containment rather than equality.\n\nThis describes the ARTIFACTS a run leaves behind. To chain stages, match on `taskType` and the molecule types: a scoring stage passes its input type through even though its `outputTypes` says `score`. Absent means the tool declares nothing, which is unknown rather than \"produces nothing\"; and a type here is not proof the tool generated it, since a scoring tool can echo its input.",
            "items": {
              "type": "string"
            },
            "example": [
              "pdb"
            ]
          },
          "taskType": {
            "type": "string",
            "description": "The tool's pipeline task category (`structure-prediction`, `inverse-folding`, `score`, …) — the fact pipelines chain on. Absent for a tool that declares none.",
            "example": "structure-prediction"
          },
          "filterMetrics": {
            "type": "array",
            "description": "Metric names accepted in a pipeline stage's filter settings. Deliberately narrower than `outputs.columns` — `/submit-pipeline` rejects a filter on a column that is not filterable. Absent for tools whose filters are not validated.",
            "items": {
              "type": "string"
            }
          },
          "outputs": {
            "type": "object",
            "description": "The tool's result table, when it declares one. (The tool's `taskType` is a TOP-LEVEL field, not nested here.)",
            "properties": {
              "produces": {
                "type": "array",
                "description": "Molecular representations the output carries — as a column of the result table OR as a file written alongside it — INCLUDING any carried over from the input, so a scoring tool can list one here.",
                "items": {
                  "type": "string"
                }
              },
              "mainCSV": {
                "type": "string",
                "description": "Filename of the primary results CSV."
              },
              "byTask": {
                "type": "object",
                "description": "For a tool whose output depends on the task it runs: the per-task contract, keyed by task value, and authoritative for the task you set. Each entry is COMPLETE — a task that does not redeclare `mainCSV` or `taskType` inherits the tool's top-level value (the top-level `taskType` field and this block's `mainCSV`).\n\nThe top-level `taskType` and this block's `mainCSV`/`produces` summarize ACROSS tasks — `produces` is the union over every task's table, and `taskType`/`mainCSV` are the tool's top-level declaration, which is not guaranteed to be the task selector's default. Do not read them as describing the run you are about to submit.",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "taskType": {
                      "type": "string"
                    },
                    "mainCSV": {
                      "type": "string"
                    },
                    "generates": {
                      "type": "array",
                      "description": "What this task creates FRESH — distinct from the sibling `produces`, which is what the result table contains. Empty for a scoring task, whose table may still echo its input.",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "byTaskNote": {
                "type": "string",
                "description": "Present alongside `byTask`: restates in prose how to read the per-task contract against the across-task scalars."
              },
              "columns": {
                "type": "array",
                "description": "Columns of the main CSV. To FILTER on one in a pipeline, check `filterMetrics` — not every column is filterable.",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "Column name as it appears in the CSV."
                    },
                    "type": {
                      "type": "string",
                      "description": "Column type: pdb, sequence, number, string, …"
                    },
                    "displayName": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "units": {
                      "type": "string"
                    },
                    "scoringPropertyName": {
                      "type": "string",
                      "description": "Name this metric is stored under when results are ingested, for tools that expose the column as a scoring property."
                    },
                    "recommendedRange": {
                      "type": "array",
                      "description": "Advisory [min, max] for a good value; either end may be an empty string when unbounded on that side.",
                      "items": {}
                    },
                    "lowIsGood": {
                      "type": "boolean",
                      "description": "Present and true when a LOWER value is better (RMSD, PAE, energy) — the direction to rank in."
                    },
                    "tasks": {
                      "type": "array",
                      "description": "Present only on a task-gated tool: the tasks whose results include this column. An UNTAGGED column is simply not task-gated in the tool's declaration, which does not promise every task fills it.",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "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."
          },
          "job_name": {
            "type": "string",
            "description": "The name the job would actually be STORED under, present whenever you sent a `jobName`. Submission strips everything outside `[A-Za-z0-9_\\s.-]` and turns whitespace into `_`, so \"PD-L1 binder #3\" is stored as \"PD-L1_binder_3\". Key every later lookup (`/jobs`, `/result`, `/job-logs`) on THIS value — the original name answers \"not found\"."
          },
          "job_name_changed": {
            "type": "boolean",
            "description": "Present and true only when `job_name` differs from the `jobName` you sent."
          },
          "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."
          }
        }
      },
      "DiagnosticCode": {
        "type": "string",
        "enum": [
          "parse-error",
          "shape-violation",
          "unsupported-field",
          "invalid-type",
          "missing-field",
          "invalid-option",
          "invalid-filter-shape",
          "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"
      },
      "LegacyJobNamesPage": {
        "properties": {
          "jobs": {
            "items": {
              "$ref": "#/components/schemas/LegacyJobSummary"
            },
            "type": "array",
            "title": "Jobs"
          },
          "notFound": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Notfound"
          },
          "statuses": {
            "additionalProperties": {
              "type": "integer"
            },
            "type": "object",
            "title": "Statuses"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "jobs",
          "notFound",
          "statuses"
        ],
        "title": "LegacyJobNamesPage",
        "description": "The batched exact-name lookup: one row per name that resolved, and the names that did not.\n\nA miss is reported per name rather than as the whole-request 400 a single-name lookup returns,\nbecause \"which of these already exist\" is the question this shape exists to answer -- a caller\nresuming an interrupted campaign needs the misses as data, not as an error.\n\nExactly one row per resolved name, the newest. Under `organization=true` a name can match once\nper visible member; this shape answers the most recent, and the single-name read is where every\nmatch is available. That is also what bounds the response: rows cannot outnumber the names\nasked for, and every `JobName` returned came from the request body."
      },
      "LegacyJobNamesRequest": {
        "properties": {
          "jobNames": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Jobnames"
          },
          "organization": {
            "type": "boolean",
            "title": "Organization",
            "default": false
          },
          "includeSubjobs": {
            "type": "boolean",
            "title": "Includesubjobs",
            "default": false
          },
          "jobEmail": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Jobemail"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "jobNames"
        ],
        "title": "LegacyJobNamesRequest",
        "description": "The batched exact-name lookup's request body.\n\nThe names travel in a body rather than a query string because they do not fit in one: an exact\nname may be 200 characters, and the deployment stops accepting a URL well before a useful number\nof them, with a proxy-level 414 no handler can turn into a usable error. `extra=\"forbid\"` so a\nmisspelled field is refused rather than silently ignored."
      },
      "LegacyJobPage": {
        "properties": {
          "jobs": {
            "items": {
              "$ref": "#/components/schemas/LegacyJobSummary"
            },
            "type": "array",
            "title": "Jobs"
          },
          "startKey": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Startkey"
          },
          "statuses": {
            "additionalProperties": {
              "type": "integer"
            },
            "type": "object",
            "title": "Statuses"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "jobs",
          "statuses"
        ],
        "title": "LegacyJobPage"
      },
      "LegacyJobSummary": {
        "properties": {
          "JobName": {
            "type": "string",
            "title": "Jobname"
          },
          "JobStatus": {
            "type": "string",
            "title": "Jobstatus"
          },
          "Created": {
            "type": "string",
            "title": "Created"
          },
          "Type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Type"
          },
          "Settings": {
            "$ref": "#/components/schemas/LegacySettingsValue"
          },
          "Score": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Score"
          },
          "Started": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started"
          },
          "Completed": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Completed"
          },
          "Batch": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Batch"
          },
          "TamarindSchemaVersion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Tamarindschemaversion"
          },
          "WeightedHours": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Weightedhours"
          },
          "User": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "User"
          },
          "batchStatus": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "Running",
                  "Aggregating",
                  "Complete",
                  "Stopped",
                  "AggregationFailed"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Batchstatus"
          },
          "AggregationError": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Aggregationerror"
          },
          "resultUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Resulturl"
          },
          "aggregateStatus": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "InProgress",
                  "Complete",
                  "Failed"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Aggregatestatus"
          },
          "aggregateJobName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Aggregatejobname"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "JobName",
          "JobStatus",
          "Created"
        ],
        "title": "LegacyJobSummary",
        "description": "Fields published by the old handler after its explicit keep-list and enrichments."
      },
      "LegacyMultiJob": {
        "additionalProperties": {
          "anyOf": [
            {
              "$ref": "#/components/schemas/LegacyJobSummary"
            },
            {
              "additionalProperties": {
                "type": "integer"
              },
              "type": "object"
            }
          ]
        },
        "type": "object",
        "title": "LegacyMultiJob",
        "description": "The retired array-object spread emitted for an organization name collision.\n\nThis odd shape is intentionally isolated to the one historical edge case instead of weakening\nthe normal page or single-job DTOs."
      },
      "LegacySettingsValue": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "integer"
          },
          {
            "type": "number"
          },
          {
            "type": "boolean"
          },
          {
            "items": {},
            "type": "array"
          },
          {
            "additionalProperties": true,
            "type": "object"
          },
          {
            "type": "null"
          }
        ]
      },
      "LegacySingleJob": {
        "properties": {
          "JobName": {
            "type": "string",
            "title": "Jobname"
          },
          "JobStatus": {
            "type": "string",
            "title": "Jobstatus"
          },
          "Created": {
            "type": "string",
            "title": "Created"
          },
          "Type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Type"
          },
          "Settings": {
            "$ref": "#/components/schemas/LegacySettingsValue"
          },
          "Score": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Score"
          },
          "Started": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started"
          },
          "Completed": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Completed"
          },
          "Batch": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Batch"
          },
          "TamarindSchemaVersion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Tamarindschemaversion"
          },
          "WeightedHours": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Weightedhours"
          },
          "User": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "User"
          },
          "batchStatus": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "Running",
                  "Aggregating",
                  "Complete",
                  "Stopped",
                  "AggregationFailed"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Batchstatus"
          },
          "AggregationError": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Aggregationerror"
          },
          "resultUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Resulturl"
          },
          "aggregateStatus": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "InProgress",
                  "Complete",
                  "Failed"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Aggregatestatus"
          },
          "aggregateJobName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Aggregatejobname"
          },
          "statuses": {
            "additionalProperties": {
              "type": "integer"
            },
            "type": "object",
            "title": "Statuses"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "JobName",
          "JobStatus",
          "Created",
          "statuses"
        ],
        "title": "LegacySingleJob"
      },
      "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."
      },
      "NodeRunStatus": {
        "type": "string",
        "enum": [
          "waiting",
          "queued",
          "running",
          "finished",
          "failed",
          "skipped",
          "stopped",
          "cancelled"
        ],
        "title": "NodeRunStatus"
      },
      "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": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/PublicMoleculeBinding"
          },
          {
            "$ref": "#/components/schemas/PublicFileBinding"
          }
        ],
        "title": "PublicBinding"
      },
      "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."
      },
      "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 after the import is queued."
      },
      "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"
          },
          "field": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Field"
          },
          "message": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message"
          }
        },
        "type": "object",
        "required": [
          "code",
          "severity",
          "node",
          "message"
        ],
        "title": "PublicDiagnostic"
      },
      "PublicDuplicateTemplateRequest": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "version": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Version",
            "description": "A version handle e.g. 'v3'; absent -> default."
          }
        },
        "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": "v2"
        }
      },
      "PublicFastaMode": {
        "type": "string",
        "enum": [
          "one-entity-per-file",
          "one-entity-per-header"
        ],
        "title": "PublicFastaMode",
        "description": "The public spelling of the ingestion worker's FASTA split modes."
      },
      "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": {
          "file": {
            "type": "string",
            "title": "File",
            "description": "A file path (relative to your user folder)."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "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",
            "description": "CSV imports (including `fileFormat: auto` with a `.csv` filename) may be up to 2 GiB. All other formats, including files left for content-based auto-detection, are limited to 16 MiB."
          },
          "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"
          },
          "groupId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Groupid"
          },
          "groupName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Groupname"
          },
          "fileName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Filename"
          },
          "fileFormat": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Fileformat"
          },
          "moleculeIds": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Moleculeids"
          },
          "moleculeCount": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Moleculecount"
          }
        },
        "type": "object",
        "required": [
          "importId",
          "status",
          "groupId",
          "groupName",
          "fileName",
          "fileFormat",
          "moleculeIds",
          "moleculeCount"
        ],
        "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",
            "description": "The molecule's chains: chain id -> the chain's sequence — amino acids for a protein, nucleotides for a nucleic acid, or a SMILES string for a small molecule. `chainMapping[<chain id>].type` says which."
          },
          "chainMapping": {
            "additionalProperties": {
              "$ref": "#/components/schemas/MoleculeChainInfo"
            },
            "type": "object",
            "title": "Chainmapping",
            "description": "Per-chain detail, keyed by the same chain ids as `entity`: its `type` and any `tags`."
          },
          "files": {
            "additionalProperties": {
              "items": {
                "$ref": "#/components/schemas/MoleculeFileEntry"
              },
              "type": "array"
            },
            "type": "object",
            "title": "Files",
            "description": "Structures and other artifacts, keyed by the PRODUCER that made them — a tool name (`alphafold`, `protenix`) or `user` for your own uploads. Each entry carries a short-lived signed `downloadUrl`. This is where `hasStructure: true` resolves to an actual file. It can be EMPTY on the org-wide listing even for a molecule that has a structure: with no group to relabel a chain-bearing file's chains against, those files are served without a link and dropped — pass `group`, or read the molecule by id."
          },
          "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata",
            "description": "TWO namespaces in one map, and knowing which is which is what makes `filter` work.\n\nA key naming a TOOL holds a LIST of that tool's score runs — `{\"protenix\": [{\"jobId\": \"...\", \"runId\": \"...\", \"ipSAE_AB\": 0.62}]}`. Any other key is a SCALAR annotation you set yourself — `{\"affinity_nM\": 12.4}`.\n\nFilter and sort address the two differently: a tool score is the DOTTED path `protenix.ipSAE_AB` (`?filter=protenix.ipSAE_AB:gte:0.5`), and a scalar is the BARE key (`?filter=affinity_nM:lte:50`). Metric names are case-sensitive and must match what you see here. `exists` applies to scalar keys only; to ask whether a tool reported a metric, filter on its value instead.\n\nEach run entry carries `jobId` AND `runId`, which answer different questions. `jobId` is the DISPATCH that produced the entry, and the engine dispatches one job per molecule — so the same run has a different `jobId` on every molecule and grouping a page by it yields one group per molecule. `runId` is the run — the pipeline NODE RUN, where there is one — and is the field to group by. Pass it straight back as `nodeRunId` to list everything that node run produced; the `nr-` prefix a pipeline run carries is accepted as-is, and `nodeRuns[].id` (the bare uuid) names the same run."
          },
          "truncated": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Truncated",
            "description": "Which inline collections were CAPPED for this molecule, so you know you are not looking at all of it: `\"scores\"` (more score runs than returned), `\"files\"`, or `\"groups\"` (page the rest via `GET /molecules/{moleculeId}/groups`). Empty means the maps above are the molecule's full accumulated history."
          },
          "createdAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Createdat"
          },
          "addedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Addedat"
          },
          "createdBy": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Createdby"
          },
          "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"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Tags"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notes"
          },
          "hasStructure": {
            "type": "boolean",
            "title": "Hasstructure",
            "description": "Whether this molecule has a structure at all (a pdb/cif, or an sdf). The files themselves are in `files`, keyed by the tool that produced them — but see that field: on the org-wide listing `files` can be empty even when this is true, because chain-bearing files need a group to relabel against. Pass `group`, or read the molecule by id, to get the download."
          },
          "matchedOn": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Matchedon"
          },
          "sortGroup": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicMoleculeSortGroup"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "entity",
          "chainMapping",
          "files",
          "metadata",
          "truncated",
          "createdAt",
          "addedAt",
          "createdBy",
          "origin",
          "source",
          "groups",
          "tags",
          "notes",
          "hasStructure",
          "matchedOn"
        ],
        "title": "PublicMolecule",
        "description": "One molecule, everything inline — no follow-up call to read scores."
      },
      "PublicMoleculeBinding": {
        "properties": {
          "group": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Group",
            "description": "An existing molecules group id."
          },
          "sequences": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sequences",
            "description": "Raw protein or nucleic-acid sequences to make a group from."
          },
          "smiles": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Smiles",
            "description": "Raw small-molecule SMILES to make a group from."
          },
          "pdbs": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pdbs",
            "description": "Uploaded .pdb file paths (relative to your user folder) to make a protein or nucleic-acid group from."
          },
          "sdfs": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sdfs",
            "description": "Uploaded .sdf file paths (relative to your user folder) to make a group from."
          },
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Name",
            "description": "Name for the group created from raw values (auto if omitted)."
          },
          "chainMapping": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Chainmapping",
            "description": "Optional; defaults to identity. referenceChain -> yourChain — only needed for an existing template whose reference chain IDs differ from your molecule's (for an inline pipeline your molecule IS the reference, so omit it)."
          },
          "residuesByChain": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Residuesbychain",
            "description": "Residue selection for this input, in ONE of two shapes (auto-detected by the value: a reference chain maps to a range STRING => SIMPLE; a tool node id maps to a per-field OBJECT => ADVANCED):\n- SIMPLE fan-out: `{ referenceChain -> ranges }` (e.g. `{\"A\": \"1-76\"}`). ONE selection applied to every residue field this input feeds. Used when the template maker turned on 'Simplified residue selection'.\n- ADVANCED per-field: `{ toolNodeId -> { field -> { referenceChain -> ranges } } }` — pick residues independently per (tool node, field) that this input feeds; two settings on the same chain can differ. Advanced is the DEFAULT for newly created templates. Picks under each binding are merged across bindings into one map.\nEach range value is a string of space/comma-separated residue numbers and inclusive ranges, e.g. `\"1-76\"` or `\"42 43 44 58 59\"` or `\"10-20,45,60-64\"`."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicMoleculeBinding",
        "description": "A molecule binding. Provide the molecules ONE of these ways (exactly one):\n\n- `group` — an existing molecules group id, OR\n- `sequences` — raw protein or nucleic-acid sequences, OR\n- `smiles` — raw small-molecule SMILES, OR\n- `pdbs` / `sdfs` — paths (relative to your user folder) of already-uploaded structure files.\n\nFor the raw-value forms the server creates a molecules group for you (optionally named via `name`),\nthen binds it — so you don't have to pre-create one. The target input node declares the polymer\ntype for sequences/PDBs; SMILES/SDFs require a small-molecule target."
      },
      "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": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array",
                "maxItems": 64
              },
              {
                "type": "null"
              }
            ],
            "title": "Tags",
            "description": "Tags to add to the molecule. Existing tags are preserved; omission or an empty list leaves them unchanged."
          }
        },
        "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 job behind a tool-produced\nmolecule, `jobType` being the tool; for a plain batch submission `jobId` is the batch's own\nid rather than the run inside it, and it is that id `GET /molecules?jobId=` takes. A split\ndesign batch carries one of its runs' ids instead, as do molecules ingested before batches\nbegan naming the parent.\nBOTH 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"
          },
          "mode": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Mode"
          },
          "scanProgress": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Scanprogress"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor",
          "mode",
          "scanProgress"
        ],
        "title": "PublicMoleculePage",
        "description": "The spec's `MoleculePage` — `{items, nextCursor}` plus the three\nSEARCH-MODE fields below, and nothing else.\n\nDeliberately NOT `Page[PublicMolecule]`: the generic carries a 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. The\nenvelope grew for `mode=sequence`, which is a CHUNKED scan and therefore has\nto say two things a plain page cannot: which search actually ran, and how far\nthrough the scan this page got."
      },
      "PublicMoleculeSortGroup": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "title": "PublicMoleculeSortGroup",
        "description": "`Molecule.sortGroup` — the group this row was ORDERED BY, under `sortBy=groupName`.\n\nA molecule can be in many of your groups, so `sortBy=groupName` has to rank it by ONE\nof them: the alphabetically first EFFECTIVE (displayed) name across all your in-scope\nmemberships. That group is what makes a group's rows arrive contiguously, and it is\nthe group a grouped presentation should file the row under.\n\nIt is served because the inline `groups` list cannot be trusted to contain it: that\nlist is CAPPED and selected by membership recency, so a molecule in more groups than\nthe cap can be ranked by a group the response never carries. Re-deriving the section\nby name-sorting `groups` then files the row under the wrong heading — silently, and\nwith no way for a client to tell.\n\n`name` is the EFFECTIVE name (the rename label when one exists, else the canonical\none) — the exact string the ordering compared, so a section header built from it\ncannot disagree with the position the row was served in. That makes it the\n`displayName`-preferring sibling of `Group.name`, which is always the raw canonical\ncolumn; for a group that was never renamed the two are identical."
      },
      "PublicNodeRun": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "nodeId": {
            "type": "string",
            "title": "Nodeid",
            "description": "The stable pipeline node id."
          },
          "label": {
            "type": "string",
            "title": "Label"
          },
          "nodeType": {
            "type": "string",
            "title": "Nodetype"
          },
          "status": {
            "$ref": "#/components/schemas/NodeRunStatus"
          },
          "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 node run produced."
          }
        },
        "type": "object",
        "required": [
          "id",
          "nodeId",
          "label",
          "nodeType",
          "status",
          "startedAt",
          "completedAt",
          "outputCount",
          "jobsTotal",
          "jobsComplete",
          "outputGroup"
        ],
        "title": "PublicNodeRun"
      },
      "PublicNodeRunMolecule": {
        "properties": {
          "complexId": {
            "type": "string",
            "title": "Complexid",
            "description": "The molecule id. Called `id` on the `/molecules` endpoints, which describe the same molecule in a richer shape — `GET /molecules/{id}`, or `GET /molecules?nodeRunId=` for this whole node run's outputs at once."
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "moleculeType": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Moleculetype",
            "description": "protein, small_molecule, nucleic_acid, …"
          },
          "sequence": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sequence",
            "description": "The ':'-joined chain sequences."
          },
          "scores": {
            "additionalProperties": true,
            "type": "object",
            "title": "Scores",
            "description": "Per-tool scores keyed by tool, e.g. `{\"protenix\": {\"ipSAE_AB\": 0.62, \"pDockQ_AB\": 0.41}}`. Metric names are case-sensitive. To THRESHOLD or RANK on one of these, use `GET /molecules?nodeRunId=` with `filter=protenix.ipSAE_AB:gte:0.5` and `sortBy=protenix.ipSAE_AB` — this endpoint returns them but does not filter or sort by them."
          },
          "hasStructure": {
            "type": "boolean",
            "title": "Hasstructure",
            "description": "Whether this molecule has a structure. The FILES are not on this shape — read them from `GET /molecules?nodeRunId=...&group=...` or `GET /molecules/{complexId}`, whose `files` map carries a signed `downloadUrl` per artifact."
          }
        },
        "type": "object",
        "required": [
          "complexId",
          "name",
          "moleculeType",
          "sequence",
          "scores",
          "hasStructure"
        ],
        "title": "PublicNodeRunMolecule",
        "description": "One molecule a node run produced.\n\nRead from the node run's passing outputs directly, NOT from `PublicNodeRun.outputGroup` — which\nis why this exists. A node run's `outputGroup` is the group the node minted, and a node that enriches its\ninputs in place (scoring, structure prediction) mints none: its molecules never moved, so they\nstay in the group they came from and `outputGroup` is correctly null. A filter node has no group\neither — its survivors exist only as outputs. Reading results by group therefore reports\n\"produced nothing\" for exactly the node runs that produced the most interesting thing."
      },
      "PublicNodeRunMoleculePage": {
        "properties": {
          "molecules": {
            "items": {
              "$ref": "#/components/schemas/PublicNodeRunMolecule"
            },
            "type": "array",
            "title": "Molecules"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor",
            "description": "Pass as `cursor` for the next page."
          },
          "inputGroupId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Inputgroupid",
            "description": "The derived molecular upstream group id; it may be a superset of this node's inputs."
          },
          "inputGroupExact": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Inputgroupexact",
            "description": "Whether inputGroupId contains exactly the molecules this node processed. False means it is a superset; null means there is no single molecular upstream group."
          }
        },
        "type": "object",
        "required": [
          "molecules",
          "nextCursor",
          "inputGroupId",
          "inputGroupExact"
        ],
        "title": "PublicNodeRunMoleculePage"
      },
      "PublicPublishRequest": {
        "properties": {
          "version": {
            "type": "string",
            "minLength": 1,
            "title": "Version",
            "description": "The version handle to publish e.g. 'v1' (from a prior response's version)."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "version"
        ],
        "title": "PublicPublishRequest",
        "example": {
          "version": "v1"
        }
      },
      "PublicPublishResponse": {
        "properties": {
          "templateId": {
            "type": "string",
            "title": "Templateid"
          },
          "isPublished": {
            "type": "boolean",
            "title": "Ispublished"
          },
          "publishedVersion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Publishedversion",
            "description": "The published version handle e.g. 'v1'."
          }
        },
        "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": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Templateversion",
            "description": "The executed version handle e.g. 'v1'."
          },
          "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})."
          },
          "nodeRuns": {
            "items": {
              "$ref": "#/components/schemas/PublicNodeRun"
            },
            "type": "array",
            "title": "Noderuns"
          }
        },
        "type": "object",
        "required": [
          "id",
          "templateId",
          "templateVersion",
          "name",
          "source",
          "status",
          "startedAt",
          "completedAt",
          "inputs",
          "nodeRuns"
        ],
        "title": "PublicRun"
      },
      "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"
      },
      "PublicRunResults": {
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "ready",
              "failed"
            ],
            "title": "Status",
            "description": "`processing` (building / not finished), `ready` (download `url` set), or `failed`."
          },
          "url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url",
            "description": "A short-lived signed download URL — present only when `status` is `ready`."
          },
          "nodeRunId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Noderunid",
            "description": "The scoped node-run id, echoed back when `?nodeRunId=` was supplied."
          }
        },
        "type": "object",
        "required": [
          "status",
          "url",
          "nodeRunId"
        ],
        "title": "PublicRunResults",
        "description": "`GET /runs/results` — the run's (or one node's) output ZIP, produced asynchronously.\n\nPoll this: `processing` while the archive is being built (or the node run isn't finished yet),\n`ready` with a short-lived signed `url` once it's available, `failed` if the build failed. Polling\nis idempotent — it never starts a duplicate build.\n\n`url`/`nodeRunId` carry NO default (this surface's required-at-construction convention, see the\nmodule docstring): both are always present in the response — `null` when not applicable (`url`\nunless `ready`; `nodeRunId` unless a node run was requested) — so a client can rely on the\nstable key set."
      },
      "PublicRunSummary": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "templateId": {
            "type": "string",
            "title": "Templateid"
          },
          "templateVersion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Templateversion",
            "description": "The executed version handle e.g. 'v1'."
          },
          "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"
      },
      "PublicScoreObservation": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "moleculeId": {
            "type": "string",
            "title": "Moleculeid"
          },
          "scoreRunId": {
            "type": "string",
            "title": "Scorerunid"
          },
          "tool": {
            "type": "string",
            "title": "Tool"
          },
          "jobId": {
            "type": "string",
            "title": "Jobid"
          },
          "jobName": {
            "type": "string",
            "title": "Jobname"
          },
          "dimensions": {
            "additionalProperties": true,
            "type": "object",
            "title": "Dimensions"
          },
          "metrics": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metrics"
          },
          "structureFileId": {
            "type": "string",
            "title": "Structurefileid"
          },
          "selected": {
            "type": "boolean",
            "title": "Selected"
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "moleculeId",
          "scoreRunId",
          "tool",
          "jobId",
          "jobName",
          "dimensions",
          "metrics",
          "structureFileId",
          "selected",
          "createdAt"
        ],
        "title": "PublicScoreObservation",
        "description": "One retained prediction beneath a molecule's score run."
      },
      "PublicScoreObservationPage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicScoreObservation"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          }
        },
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "title": "PublicScoreObservationPage"
      },
      "PublicScoreObservationQuery": {
        "properties": {
          "jobIds": {
            "items": {
              "type": "string",
              "maxLength": 200,
              "minLength": 1
            },
            "type": "array",
            "maxItems": 100,
            "minItems": 1,
            "title": "Jobids"
          },
          "tool": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Tool"
          },
          "limit": {
            "type": "integer",
            "maximum": 200,
            "minimum": 1,
            "title": "Limit",
            "default": 200
          },
          "cursor": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 4096,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Cursor"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "jobIds"
        ],
        "title": "PublicScoreObservationQuery",
        "description": "One bounded, explicit concrete-job observation read."
      },
      "PublicSubmitPipelineRequest": {
        "properties": {
          "pipeline": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PipelineIR"
              },
              {
                "type": "null"
              }
            ],
            "description": "INLINE mode: a full pipeline IR. Mutually exclusive with `templateId`."
          },
          "templateId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Templateid",
            "description": "REFERENCE mode: an existing template id to run."
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name",
            "description": "Name for this pipeline. When `runName` is omitted, the run's JobName is derived from this (spaces removed, a random suffix appended for uniqueness)."
          },
          "runName": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Runname",
            "description": "Optional explicit JobName for THIS run. Spaces are replaced with underscores; used verbatim otherwise (no random suffix). Omit to auto-generate a unique JobName from `name`."
          },
          "version": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Version",
            "description": "REFERENCE mode: a version handle e.g. 'v1'; absent -> default."
          },
          "bindings": {
            "additionalProperties": {
              "$ref": "#/components/schemas/PublicBinding"
            },
            "type": "object",
            "maxProperties": 2048,
            "title": "Bindings",
            "description": "One binding per input slot, keyed by the slot's input-node id. Bindings may be defined as an array of sequences, smiles, or sdf/pdb files (uploaded using the /upload endpoint), or an existing molecule group id (created using /molecules/groups below)."
          },
          "settings": {
            "anyOf": [
              {
                "additionalProperties": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Settings",
            "description": "Per-node settings `{nodeId: {settingKey: value}}`. Chain and residue selections may be supplied here alongside ordinary settings. For existing templates, ordinary settings must be editable. Chain labels use reference space and follow chainMapping; inline residue selections must agree with overlapping binding selections."
          },
          "source": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Source"
              },
              {
                "type": "null"
              }
            ],
            "description": "Run source; defaults to production.",
            "default": "production"
          },
          "project": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Project",
            "description": "Organization project id to stamp on jobs this run creates."
          },
          "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": [
          "name",
          "bindings"
        ],
        "title": "PublicSubmitPipelineRequest",
        "description": "Body for `POST /pipelines/submit` AND `POST /pipelines/validate` (identical shape). Two modes,\ndiscriminated by which of `pipeline` / `templateId` you send — exactly one is required:\n\n- INLINE: send `pipeline` (a full IR) — submit creates a template you own (unpublished) and runs\n  it; every setting is yours to set in the IR. `name` names both the created pipeline and the run.\n- REFERENCE: send `templateId` (+ optional `version`) — run an existing template; `settings`\n  overrides are limited to each tool node's `metadata.editableSettings`. `name` names the run.\n\n`name` is required and doubles as the run's display name (deduplicated per pipeline). `bindings` is\nrequired in both modes. `validate` runs the SAME body without executing/persisting.",
        "example": {
          "bindings": {
            "target": {
              "group": "3f8a1c2e-5b7d-4e9f-a1b2-c3d4e5f6a7b8"
            }
          },
          "name": "binder-design v2 experiment",
          "project": "proj_…",
          "settings": {
            "design": {
              "numSequences": 32
            }
          },
          "templateId": "3f2a1c9e-8b7d-4e6f-a1b2-c3d4e5f60718",
          "version": "v1"
        }
      },
      "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": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Publishedversion",
            "description": "The published version handle e.g. 'v1' (null if none)."
          },
          "version": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Version",
            "description": "The version handle e.g. 'v1' (null if never saved)."
          },
          "simplifiedResidues": {
            "type": "boolean",
            "title": "Simplifiedresidues",
            "description": "True = one residue selection per binding fans to every field; False (default for new templates) = per (node, field) via a top-level residuesByChain."
          },
          "pipeline": {
            "$ref": "#/components/schemas/PipelineIR"
          },
          "inputs": {
            "items": {
              "$ref": "#/components/schemas/PublicInputSlot"
            },
            "type": "array",
            "title": "Inputs"
          },
          "versions": {
            "items": {
              "$ref": "#/components/schemas/PublicVersionSummary"
            },
            "type": "array",
            "title": "Versions",
            "description": "All saved versions, newest first."
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          },
          "updatedAt": {
            "type": "string",
            "title": "Updatedat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "isPublished",
          "publishedVersion",
          "version",
          "simplifiedResidues",
          "pipeline",
          "inputs",
          "versions",
          "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"
          },
          "createdBy": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Createdby"
          },
          "createdAt": {
            "type": "string",
            "title": "Createdat"
          },
          "updatedAt": {
            "type": "string",
            "title": "Updatedat"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "isPublished",
          "versionCount",
          "runCount",
          "createdBy",
          "createdAt",
          "updatedAt"
        ],
        "title": "PublicTemplateSummary"
      },
      "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",
            "const": "pending",
            "title": "Status",
            "default": "pending"
          },
          "importId": {
            "type": "string",
            "title": "Importid"
          }
        },
        "type": "object",
        "required": [
          "groupId",
          "groupName",
          "moleculeIds",
          "moleculeCount",
          "importId"
        ],
        "title": "PublicUploadResponse"
      },
      "PublicValidateResponse": {
        "properties": {
          "valid": {
            "type": "boolean",
            "title": "Valid"
          },
          "errors": {
            "items": {
              "$ref": "#/components/schemas/PublicDiagnostic"
            },
            "type": "array",
            "title": "Errors"
          }
        },
        "type": "object",
        "required": [
          "valid",
          "errors"
        ],
        "title": "PublicValidateResponse"
      },
      "PublicValidateTemplateRequest": {
        "properties": {
          "version": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Version",
            "description": "A version handle e.g. 'v1' to validate; absent -> default."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "PublicValidateTemplateRequest",
        "description": "Body for `POST /templates/{id}/validate` — validate the TEMPLATE ITSELF (no run bindings),\nagainst its own `metadata.defaultGroup` reference groups. `{}` is valid (validate the default\nversion)."
      },
      "PublicVersionSummary": {
        "properties": {
          "version": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Version",
            "description": "The version handle e.g. 'v1'."
          },
          "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": [
          "production",
          "test"
        ],
        "title": "Source"
      },
      "PublicProblem": {
        "additionalProperties": false,
        "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"
          },
          "retryable": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Whether the SAME request can succeed later. `true` means retry (honour `Retry-After` when present); `false` means this will not resolve on its own and retrying cannot change it. Omitted when the error does not specify — absence is NOT `false`.",
            "title": "Retryable"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "title": "PublicProblem",
        "type": "object"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is valid HTTP but uses an unsupported parameter combination.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "API key missing or invalid.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The authenticated API key is not allowed to use this operation.",
        "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"
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with current resource state.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "The operation could not be served. Read `retryable` in the response body before retrying: `true` means the same request can succeed later (honour `Retry-After` when present), `false` means it will not resolve on its own. When `retryable` is absent, treat the condition as transient and back off.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        },
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying, sent only when a retry is expected to help. Its ABSENCE on a 503 is meaningful — check `retryable` in the body.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        }
      },
      "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"
    }
  ]
}