{
  "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-8qm0wag7i-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 the complete array of file names for the selected folder. This endpoint does not paginate.",
        "operationId": "getFiles",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "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": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Files"
        ]
      }
    },
    "/api/agent/provision": {
      "post": {
        "summary": "Get an API key with no account — for AI agents",
        "description": "Mints a working API key with no authentication, no signup form, no email, no CAPTCHA and no payment method. POST with an empty body. Intended for an autonomous agent that has found this API and has no credential and no human available.\n\nWhat comes back is a real free account with EVERY tool available and the FULL free monthly allowance — not a restricted sandbox and not a fraction of a tier. Do not substitute a simpler tool, or a smaller run, than the one you were asked for. `POST /api/validate-job` costs nothing and does NOT consume a job, so use it to get the payload right first. The response's `limits` is authoritative.\n\nThe one difference from a signed-up account is that an unclaimed credential EXPIRES (see `expiresAt`). `/api/submit-job` and `/api/submit-batch` both work; `/api/submit-pipeline`, `/api/run-pipeline`, `/api/virtual-screening` and `/api/deploy-model` return `403` until a human claims it. That is this limit firing, not a malformed request, so do not retry with a different payload.\n\nThe response also carries a `claimUrl`. Give it to the person you are working for. One sign-in there MOVES the job you ran into that person's account so they can see it, and releases the rest of the monthly free tier. **The same API key keeps working**, so nothing already written against it has to change; afterwards it acts as that user. Unclaimed credentials stop working when `expiresAt` passes.\n\nStore the key you receive. Provisioning is rate-limited per source and platform-wide; asking for a second key because the first was not kept is the usual cause of a `429`. Full agent-authentication notes are at `/auth.md` on this host.",
        "operationId": "provisionAgentCredential",
        "security": [],
        "responses": {
          "201": {
            "description": "A trial credential was issued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiKey": {
                      "type": "string",
                      "description": "Send as the `x-api-key` header on every subsequent request."
                    },
                    "email": {
                      "type": "string",
                      "description": "The synthetic identity this credential belongs to, on a pseudo-TLD that cannot be registered by anyone. It is not a mailbox and nothing is sent to it."
                    },
                    "tier": {
                      "type": "string",
                      "example": "agent-trial"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "After this instant an unclaimed credential stops authenticating."
                    },
                    "limits": {
                      "type": "object",
                      "description": "Authoritative limits for THIS credential. Read these rather than assuming fixed values — they can change without a spec revision.",
                      "properties": {
                        "jobs": {
                          "type": "integer",
                          "description": "Monthly job allowance — the ordinary free tier.",
                          "example": 10
                        },
                        "allowedTools": {
                          "type": "string",
                          "description": "`\"all\"` — not tool-restricted. Present so a client does not have to infer it from the absence of a list.",
                          "example": "all"
                        },
                        "maxRuntimeSeconds": {
                          "type": "integer",
                          "nullable": true,
                          "description": "`null` — no agent-specific runtime cap; jobs are clamped by the ordinary free-tier rules."
                        },
                        "expiresInDays": {
                          "type": "integer",
                          "description": "How long the credential works if never claimed.",
                          "example": 30
                        }
                      }
                    },
                    "claimUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Surface this to your human. One sign-in releases the rest of the free tier on the same account, without changing the API key."
                    },
                    "usage": {
                      "type": "object",
                      "description": "The endpoints to call next, on the host that issued this key."
                    },
                    "nextSteps": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "notice": {
                      "type": "string",
                      "description": "Plain-language statement of what this credential can and cannot do."
                    },
                    "terms": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-source or platform-wide provisioning limit reached. Reuse a key you already hold; otherwise honour `retryAfterSeconds` — do not retry in a loop — or have a human sign up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Provisioning is temporarily unavailable. The platform-wide counter fails CLOSED, so this is returned rather than issuing an uncounted credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/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"
                }
              ],
              "title": "Filter"
            }
          },
          {
            "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 includes its chains, scores, metadata, and files inline. Pass `scope=org` to list across your whole organization.\n\nSearch by `group`, `jobId`/`jobName`, tool, name, or protein-sequence / SMILES subsequence. Filter or sort by tool scores (e.g. `alphafold.ptm`).\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 that isn't yours returns an empty page.",
              "title": "Group"
            },
            "description": "Limit to one group (by id). A group that isn't yours returns an empty page."
          },
          {
            "name": "jobId",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit to one job's molecules (produced or consumed). Pass a batch id, not a child job id.",
              "title": "Jobid"
            },
            "description": "Limit to one job's molecules (produced or consumed). Pass a batch id, not a child job id."
          },
          {
            "name": "jobName",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Same as `jobId`, by job/batch name. Names aren't unique — all visible matches are included.",
              "title": "Jobname"
            },
            "description": "Same as `jobId`, by job/batch name. Names aren't unique — all visible matches are included."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MoleculeType"
                  }
                },
                {
                  "type": "null"
                }
              ],
              "description": "Limit by molecule kind (e.g. protein, small_molecule, nucleic_acid).",
              "title": "Type"
            },
            "description": "Limit by molecule kind (e.g. protein, small_molecule, nucleic_acid)."
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 10000
                },
                {
                  "type": "null"
                }
              ],
              "description": "Search term. `mode=default` (or `name`) matches molecule names, metadata keys, and score metrics; ungrouped searches require at least 3 characters. `mode=sequence` matches an amino-acid sequence of at least 3 characters.",
              "title": "Search"
            },
            "description": "Search term. `mode=default` (or `name`) matches molecule names, metadata keys, and score metrics; ungrouped searches require at least 3 characters. `mode=sequence` matches an amino-acid sequence of at least 3 characters."
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(default|name|sequence)$",
              "description": "How `search` is read. `default` (alias `name`) matches names, metadata keys, and score metrics. With no `sortBy`, `filter`, or `sequence`, it is a complete chunked scan: follow `nextCursor` until null, including through empty or short pages. Adding any of those options uses capped ranked pagination, so a null cursor completes that page, not every possible name match. `sequence` is a complete chunked scan over amino-acid chains. Check the echoed `mode`.",
              "default": "default",
              "title": "Mode"
            },
            "description": "How `search` is read. `default` (alias `name`) matches names, metadata keys, and score metrics. With no `sortBy`, `filter`, or `sequence`, it is a complete chunked scan: follow `nextCursor` until null, including through empty or short pages. Adding any of those options uses capped ranked pagination, so a null cursor completes that page, not every possible name match. `sequence` is a complete chunked scan over amino-acid chains. Check the echoed `mode`."
          },
          {
            "name": "sequenceMatch",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^(subsequence|exact)$",
              "description": "With `mode=sequence`, how to match the term: `subsequence` (default) — chains contain it; `exact` — a chain equals it. Rejected in name mode.",
              "default": "subsequence",
              "title": "Sequencematch"
            },
            "description": "With `mode=sequence`, how to match the term: `subsequence` (default) — chains contain it; `exact` — a chain equals it. Rejected in name mode."
          },
          {
            "name": "examplesPerGroup",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 25,
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Cap how many molecules each group contributes to a page (requires `sortBy=groupName`). Not allowed with `mode=sequence`.",
              "title": "Examplespergroup"
            },
            "description": "Cap how many molecules each group contributes to a page (requires `sortBy=groupName`). Not allowed with `mode=sequence`."
          },
          {
            "name": "sequence",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Match molecules containing this amino-acid subsequence in any chain (case-insensitive, at least 3 characters).",
              "title": "Sequence"
            },
            "description": "Match molecules containing this amino-acid subsequence in any chain (case-insensitive, at least 3 characters)."
          },
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "maxItems": 32
                },
                {
                  "type": "null"
                }
              ],
              "description": "Repeatable `field:operator:value` predicate, AND-ed, on a metadata field or tool score (`tool.metric`). Operators: `gt`, `gte`, `lt`, `lte`, `eq`, `neq`, `exists` (written `field:exists`, no value — metadata fields only). Append `:jobId` to pin a tool score to one job.",
              "title": "Filter"
            },
            "description": "Repeatable `field:operator:value` predicate, AND-ed, on a metadata field or tool score (`tool.metric`). Operators: `gt`, `gte`, `lt`, `lte`, `eq`, `neq`, `exists` (written `field:exists`, no value — metadata fields only). Append `:jobId` to pin a tool score to one job."
          },
          {
            "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.",
              "default": "mine",
              "title": "Scope"
            },
            "description": "`mine` (the default) shows molecules in groups you created; `org` shows everything across your organization."
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Sort by a built-in column (`id`, `added`, `type`, `groupName`) or a metadata field / tool score (`tool.metric`, on the tool's best run). `groupName` keeps a group's rows together — pair with `examplesPerGroup`.",
              "default": "added",
              "title": "Sortby"
            },
            "description": "Sort by a built-in column (`id`, `added`, `type`, `groupName`) or a metadata field / tool score (`tool.metric`, on the tool's best run). `groupName` keeps a group's rows together — pair with `examplesPerGroup`."
          },
          {
            "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/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}`."
          }
        ],
        "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`.",
        "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": 100,
              "minimum": 1,
              "description": "Molecules per page.",
              "default": 25,
              "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"
                }
              ]
            }
          }
        ]
      }
    }
  },
  "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"
          },
          "provisionApiKey": {
            "type": "string",
            "format": "uri",
            "description": "Present on a missing/invalid API-key rejection: the endpoint that mints a working key with no authentication, no signup form and no human. POST it with an empty body. It returns a real free account — every tool and the full monthly allowance, nothing held back — which expires unless a human claims it with one sign-in, keeping the same key. Listed first in the body because it is the only recovery an agent can complete on its own."
          },
          "agentAuth": {
            "type": "string",
            "format": "uri",
            "description": "Present on a missing/invalid API-key rejection: the agent-authentication document (`/auth.md`) describing the provisioning call, the trial credential's limits and the claim flow."
          },
          "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 gives the agent path (POST `provisionApiKey`, which needs no authentication) and the human path (create a key in the web app) separately, because the right answer differs by caller; 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."
                },
                "minItems": {
                  "type": "integer",
                  "description": "Enforced minimum array length for a `list: true` field. A shorter array is rejected with a 400. Published alongside the constraint."
                },
                "maxItems": {
                  "type": "integer",
                  "description": "Enforced maximum array length for a `list: true` field. A longer array is rejected with a 400. Published alongside the constraint."
                },
                "maxLength": {
                  "type": "integer",
                  "description": "Maximum sequence length in residues (whitespace is not counted). Exceeding it is a 400. Published for any `sequence`-type field (list-valued fields cap every entry). Limits range from 14 residues to 20,000, so check before sending a long chain."
                },
                "minLength": {
                  "type": "integer",
                  "description": "Minimum sequence length in residues (whitespace is not counted). A shorter entry is rejected with a 400. Published for any `sequence`-type field (list-valued fields floor every entry)."
                },
                "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",
          "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"
      },
      "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"
        ],
        "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"
          },
          "chainMapping": {
            "additionalProperties": {
              "$ref": "#/components/schemas/MoleculeChainInfo"
            },
            "type": "object",
            "title": "Chainmapping"
          },
          "files": {
            "additionalProperties": {
              "items": {
                "$ref": "#/components/schemas/MoleculeFileEntry"
              },
              "type": "array"
            },
            "type": "object",
            "title": "Files"
          },
          "metadata": {
            "additionalProperties": true,
            "type": "object",
            "title": "Metadata"
          },
          "truncated": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Truncated"
          },
          "createdAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Createdat"
          },
          "addedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Addedat"
          },
          "origin": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PublicMoleculeOrigin"
              },
              {
                "type": "null"
              }
            ]
          },
          "source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Source"
          },
          "groups": {
            "items": {
              "$ref": "#/components/schemas/PublicGroup"
            },
            "type": "array",
            "title": "Groups"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Tags"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notes"
          },
          "hasStructure": {
            "type": "boolean",
            "title": "Hasstructure"
          },
          "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",
          "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 producing job/batch\nfor a tool-produced molecule; BOTH are null for an upload — an upload has no job, and\nwe do not fabricate one. Read-only, projected from columns that already exist (no\nmigration)."
      },
      "PublicMoleculePage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/PublicMolecule"
            },
            "type": "array",
            "title": "Items"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nextcursor"
          },
          "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"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "moleculeType": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Moleculetype"
          },
          "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."
          },
          "hasStructure": {
            "type": "boolean",
            "title": "Hasstructure"
          }
        },
        "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."
          }
        },
        "type": "object",
        "required": [
          "molecules",
          "nextCursor"
        ],
        "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"
          }
        },
        "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": "A dependency required by this operation is temporarily unavailable.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/PublicProblem"
            }
          }
        },
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying when the server supplies a delay.",
            "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"
    }
  ]
}