{
  "openapi": "3.1.0",
  "info": {
    "title": "Verbex Lab Tools API",
    "version": "0.2.0",
    "summary": "Deterministic wet-lab calculators and document builders.",
    "description": "Verbex Lab Tools are deterministic wet-lab calculators and document builders from Verbex, the voice lab notebook for iPhone and iPad. For a calculation, call the matching calculator (solution_prep, serial_dilution, pcr_master_mix, lnp_formulation, plate_map) directly; do not search first and do not search the web for the method. Use search and fetch only to find and read Verbex guides (protocols, notebooks, inventory, data integrity, bench math) and the 16 lab documentation templates; cite the url each result returns. Call a tool for every number a scientist will act on (masses, volumes, concentrations, dilution series) instead of calculating it yourself, and report the values exactly as returned, with units and the same significant figures (never add digits). Each result carries a self-check; describe results as checked by Verbex Lab Tools, never as validated or verified. Show the tool warnings and safety notes to the user. Results are calculation aids; the scientist verifies them against their own lab requirements. Inputs are used only to compute the response. Nothing is stored, no account is needed, and no AI model is called.\n\nProduction MCP availability is listed at https://www.verbalexperiment.com/.well-known/mcp/server-card.json. Standard-curve and protein-assay analysis are beta browser/HTTP API tools, not production MCP tools.",
    "contact": {
      "name": "Verbex support",
      "email": "support@verbalexperiment.com",
      "url": "https://www.verbalexperiment.com/support"
    },
    "termsOfService": "https://www.verbalexperiment.com/privacy"
  },
  "servers": [
    {
      "url": "https://www.verbalexperiment.com"
    }
  ],
  "externalDocs": {
    "description": "Agent guide",
    "url": "https://www.verbalexperiment.com/llms.txt"
  },
  "paths": {
    "/api/tools/molarity-calculator": {
      "post": {
        "operationId": "solution_prep",
        "summary": "Solution prep (molarity and C1V1)",
        "description": "Use when someone asks how much solid to weigh for a molar solution (how many grams for X mM in Y mL, making up a buffer or salt solution), or how much stock to dilute to a target concentration (C1V1). mode \"solid\": mass of solid to weigh from molecular weight, target concentration and final volume. mode \"stock\": C1V1 = C2V2 dilution from a stock, solving for stockVolume, stockConcentration, targetConcentration or finalVolume. Returns the primary result, a step summary, warnings and safety notes.\n\nFor people: https://www.verbalexperiment.com/tools/molarity-calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MolarityCalculatorInput"
              },
              "example": {
                "mode": "stock",
                "solutionName": "100 mM Tris solution",
                "solveFor": "stockVolume",
                "stockConcentration": 1,
                "stockConcentrationUnit": "M",
                "targetConcentration": 100,
                "targetConcentrationUnit": "mM",
                "finalVolume": 100,
                "finalVolumeUnit": "mL",
                "stockVolumeUnit": "mL",
                "solvent": "water or buffer"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MolarityCalculatorOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MolarityCalculatorOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/serial-dilution-calculator": {
      "post": {
        "operationId": "serial_dilution",
        "summary": "Serial dilution planner",
        "description": "Use for any serial dilution (standard curves, titrations, dose series). Plans transfer and diluent volume per tube and the resulting concentration at each step, from a stock concentration, a constant dilution factor, the number of tubes and the final volume per tube.\n\nFor people: https://www.verbalexperiment.com/tools/serial-dilution-calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SerialDilutionCalculatorInput"
              },
              "example": {
                "seriesName": "10-fold dilution series",
                "stockConcentration": 1200,
                "concentrationUnit": "x",
                "dilutionFactor": 10,
                "steps": 3,
                "finalVolume": 100,
                "volumeUnit": "uL"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerialDilutionCalculatorOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerialDilutionCalculatorOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/pcr-master-mix-calculator": {
      "post": {
        "operationId": "pcr_master_mix",
        "summary": "PCR / qPCR master mix",
        "description": "Use when someone sets up a PCR or qPCR reaction mix for several wells. Calculates volume of each component per reaction and for the whole mix with excess, with water filling the remaining volume. Give each component a stock and final concentration (molar, ng/µL, x buffer, %), an amount per reaction (e.g. 1.25 U polymerase from 5 U/µL, 10 ng template from ng/µL), or a fixed volume. Template can be added per well separately. Do not list water; it is calculated.\n\nFor people: https://www.verbalexperiment.com/tools/pcr-master-mix-calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PcrMasterMixCalculatorInput"
              },
              "example": {
                "reactionName": "IL-6 qPCR, plate 2",
                "reactions": 46,
                "reactionVolume": 20,
                "reactionVolumeUnit": "uL",
                "excessPercent": 10,
                "templateVolume": 2,
                "components": [
                  {
                    "name": "2x SYBR Green master mix",
                    "stockConcentration": 2,
                    "stockUnit": "x",
                    "finalConcentration": 1,
                    "finalUnit": "x"
                  },
                  {
                    "name": "Forward primer",
                    "stockConcentration": 10,
                    "stockUnit": "uM",
                    "finalConcentration": 400,
                    "finalUnit": "nM"
                  },
                  {
                    "name": "Reverse primer",
                    "stockConcentration": 10,
                    "stockUnit": "uM",
                    "finalConcentration": 400,
                    "finalUnit": "nM"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PcrMasterMixCalculatorOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PcrMasterMixCalculatorOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/lnp-formulation-calculator": {
      "post": {
        "operationId": "lnp_formulation",
        "summary": "LNP formulation (lipid nanoparticles)",
        "description": "Plan a lipid nanoparticle (LNP) formulation for RNA: from RNA mass, N/P ratio and lipid mol% (e.g. ionizable lipid/DSPC/cholesterol/PEG-lipid 50/10/38.5/1.5), calculate moles, mass and stock volume of every lipid, the ethanol and aqueous phase volumes at the chosen mixing ratio (default 3:1), and final RNA concentration, ethanol fraction and lipid:RNA ratio. Molecular weights must come from the user or certificates of analysis.\n\nFor people: https://www.verbalexperiment.com/tools/lnp-formulation-calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LnpFormulationCalculatorInput"
              },
              "example": {
                "formulationName": "SM-102 LNP, luciferase mRNA",
                "rnaMass": 100,
                "rnaMassUnit": "ug",
                "rnaStockConcentration": 1,
                "rnaStockUnit": "mg/mL",
                "npRatio": 6,
                "aqueousToEthanolRatio": 3,
                "aqueousVolume": 750,
                "aqueousVolumeUnit": "uL",
                "lipids": [
                  {
                    "name": "SM-102",
                    "molPercent": 50,
                    "molecularWeight": 710.17,
                    "stockConcentration": 25,
                    "stockUnit": "mg/mL",
                    "ionizable": true
                  },
                  {
                    "name": "DSPC",
                    "molPercent": 10,
                    "molecularWeight": 790.15,
                    "stockConcentration": 10,
                    "stockUnit": "mg/mL"
                  },
                  {
                    "name": "Cholesterol",
                    "molPercent": 38.5,
                    "molecularWeight": 386.65,
                    "stockConcentration": 10,
                    "stockUnit": "mg/mL"
                  },
                  {
                    "name": "DMG-PEG2000",
                    "molPercent": 1.5,
                    "molecularWeight": 2509.2,
                    "stockConcentration": 10,
                    "stockUnit": "mg/mL"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LnpFormulationCalculatorOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LnpFormulationCalculatorOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/96-well-plate-map-builder": {
      "post": {
        "operationId": "plate_map",
        "summary": "Plate map (6- to 384-well)",
        "description": "Use when someone lays out a multiwell plate (ELISA, qPCR, cell or compound assays). Lays out a 6, 12, 24, 48, 96 or 384-well plate: standards, samples, controls and blanks with replicates, filled by row or column, optionally leaving edge wells empty and keeping replicate sets together. Returns a grid with a key and a well-by-well list.\n\nFor people: https://www.verbalexperiment.com/tools/96-well-plate-map-builder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Tool96WellPlateMapBuilderInput"
              },
              "example": {
                "plateName": "ELISA IL-6 plate 1",
                "format": 96,
                "standards": {
                  "name": "Std",
                  "points": 8,
                  "replicates": 2
                },
                "samples": [
                  "Patient 01",
                  "Patient 02",
                  "Patient 03",
                  "Patient 04",
                  "Patient 05",
                  "Patient 06",
                  "Patient 07",
                  "Patient 08",
                  "Patient 09",
                  "Patient 10"
                ],
                "replicates": 3,
                "controls": [
                  "Positive control"
                ],
                "controlReplicates": 2,
                "blanks": 2,
                "fillDirection": "column"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool96WellPlateMapBuilderOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool96WellPlateMapBuilderOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/lab-protocol-template-builder": {
      "post": {
        "operationId": "protocol_template",
        "summary": "Lab protocol template",
        "description": "Build a structured lab protocol (purpose, materials, procedure, critical steps, acceptance criteria and more) for a protocol title, using a preset or custom sections. Returns Markdown and HTML ready to fill in or print.\n\nFor people: https://www.verbalexperiment.com/tools/lab-protocol-template-builder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LabProtocolTemplateBuilderInput"
              },
              "example": {
                "protocolTitle": "ELISA protocol scaffold",
                "presetId": "elisa",
                "includeExecutionNotes": true,
                "includeVersionHistory": true,
                "sectionItems": [
                  {
                    "id": "elisaPurpose",
                    "source": "library"
                  },
                  {
                    "id": "elisaPlateLayout",
                    "source": "library"
                  },
                  {
                    "id": "elisaStandardsControls",
                    "source": "library"
                  },
                  {
                    "id": "elisaReagents",
                    "source": "library"
                  },
                  {
                    "id": "elisaProcedure",
                    "source": "library"
                  },
                  {
                    "id": "elisaAcceptanceCriteria",
                    "source": "library"
                  },
                  {
                    "id": "versionHistory",
                    "source": "library"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LabProtocolTemplateBuilderOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LabProtocolTemplateBuilderOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/eln-template-builder": {
      "post": {
        "operationId": "eln_template",
        "summary": "Electronic lab notebook entry template",
        "description": "Build an electronic lab notebook (ELN) entry template for an experiment: objective, materials, procedure, observations and conclusion sections from a preset or custom list, optionally with timestamp fields and voice-capture prompts. Returns Markdown and HTML.\n\nFor people: https://www.verbalexperiment.com/tools/eln-template-builder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ElnTemplateBuilderInput"
              },
              "example": {
                "experimentTitle": "Cell culture passage record",
                "presetId": "cellCulture",
                "includeTimestamps": true,
                "includeVoicePrompts": true,
                "sectionItems": [
                  {
                    "id": "objective",
                    "source": "library"
                  },
                  {
                    "id": "materials",
                    "source": "library"
                  },
                  {
                    "id": "procedure",
                    "source": "library"
                  },
                  {
                    "id": "observations",
                    "source": "library"
                  },
                  {
                    "id": "deviations",
                    "source": "library"
                  },
                  {
                    "id": "custom-cell-health",
                    "source": "custom",
                    "title": "Cell Health Notes"
                  },
                  {
                    "id": "review",
                    "source": "library"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ElnTemplateBuilderOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ElnTemplateBuilderOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/reagent-label-scanner": {
      "post": {
        "operationId": "parse_reagent_label",
        "summary": "Reagent label parser",
        "description": "Turn text read from a reagent bottle or kit label (OCR or typed) into structured fields such as product name, catalog number, lot, expiry, concentration and storage. Parses the provided text only; send the label text, not an image.\n\nFor people: https://www.verbalexperiment.com/tools/reagent-label-scanner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReagentLabelScannerInput"
              },
              "example": {
                "ocrText": "Cellartis\nPower Primary HEP Medium\nY20020\nLot AOX0016S\nExp OCT. 2025\nStore at -20C\n250 mL",
                "sourceImageName": "cellartis-hep-medium-label.jpg",
                "ocrConfidence": 92
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReagentLabelScannerOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReagentLabelScannerOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/standard-curve": {
      "post": {
        "operationId": "standard_curve",
        "summary": "Standard curve (ELISA, beta)",
        "description": "Use when someone has ELISA or other immunoassay plate readings and wants a standard curve, sample concentrations, or to know whether their standards fit. Fits a 4PL (5PL only when the standards support it), back-calculates every standard, sets the working range (LLOQ to ULOQ) and reports samples only inside it, with dilution factors applied; never extrapolates. Send concentrationUnit, standards as {concentration, responses: [replicate readings]}, optional blanks and samples [{name, responses, dilutionFactor}]. Default limits follow ICH M10: recovery within ±20% (±25% at LLOQ and ULOQ), replicate CV ≤ 20%, at least 75% and 6 levels passing. They are defaults: pass limits to tighten or relax them, and every result states the limits it used. Exclude a standard only with a reason the scientist gives. Beta, checked against published datasets (agreement within about 1%): report the flags and call results checked, not validated.\n\nFor people: https://www.verbalexperiment.com/tools/standard-curve",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StandardCurveInput"
              },
              "example": {
                "assayName": "IL-6 ELISA plate 1",
                "concentrationUnit": "pg/mL",
                "standards": [
                  {
                    "concentration": 4.69,
                    "responses": [
                      0.118,
                      0.124
                    ]
                  },
                  {
                    "concentration": 9.38,
                    "responses": [
                      0.171,
                      0.165
                    ]
                  },
                  {
                    "concentration": 18.75,
                    "responses": [
                      0.27,
                      0.281
                    ]
                  },
                  {
                    "concentration": 37.5,
                    "responses": [
                      0.482,
                      0.468
                    ]
                  },
                  {
                    "concentration": 75,
                    "responses": [
                      0.861,
                      0.889
                    ]
                  },
                  {
                    "concentration": 150,
                    "responses": [
                      1.512,
                      1.478
                    ]
                  },
                  {
                    "concentration": 300,
                    "responses": [
                      2.341,
                      2.398
                    ]
                  },
                  {
                    "concentration": 600,
                    "responses": [
                      2.902,
                      2.951
                    ]
                  }
                ],
                "blanks": [
                  0.052,
                  0.049
                ],
                "samples": [
                  {
                    "name": "Patient 01",
                    "responses": [
                      0.655,
                      0.671
                    ],
                    "dilutionFactor": 2
                  },
                  {
                    "name": "Patient 02",
                    "responses": [
                      3.05,
                      3.08
                    ],
                    "dilutionFactor": 2
                  },
                  {
                    "name": "Patient 02",
                    "responses": [
                      1.2,
                      1.24
                    ],
                    "dilutionFactor": 20
                  },
                  {
                    "name": "Patient 03",
                    "responses": [
                      0.06,
                      0.061
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardCurveOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardCurveOutput"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/protein-assay": {
      "post": {
        "operationId": "protein_assay",
        "summary": "Protein assay (BCA, Bradford, beta)",
        "description": "Use when someone has BCA or Bradford (or other colorimetric) protein assay readings and wants sample protein concentrations, or how much lysate to load for a western blot. Subtracts the blank, fits a quadratic standard curve (linear or 4PL on request), back-calculates every standard, and reports sample concentrations only within the working range, with dilution factors applied. Optional loading: give a protein mass per lane (e.g. 20 µg) and the lane volume to get the µL of sample and buffer for each. Send concentrationUnit (e.g. ug/mL), standards as {concentration, responses}, blanks, samples [{name, responses, dilutionFactor}]. Default limits are Verbex protein-assay defaults (recovery ±15%, ±25% at the end standards, replicate CV ≤ 15%); no regulatory guideline sets these, and every result states the limits used. Concentrations are relative to the standard protein (usually BSA). Beta: report the flags and call results checked, not validated.\n\nFor people: https://www.verbalexperiment.com/tools/protein-assay",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProteinAssayInput"
              },
              "example": {
                "assayName": "BCA, cell lysates",
                "concentrationUnit": "ug/mL",
                "standards": [
                  {
                    "concentration": 25,
                    "responses": [
                      0.08,
                      0.083
                    ]
                  },
                  {
                    "concentration": 125,
                    "responses": [
                      0.198,
                      0.192
                    ]
                  },
                  {
                    "concentration": 250,
                    "responses": [
                      0.341,
                      0.35
                    ]
                  },
                  {
                    "concentration": 500,
                    "responses": [
                      0.611,
                      0.602
                    ]
                  },
                  {
                    "concentration": 750,
                    "responses": [
                      0.85,
                      0.862
                    ]
                  },
                  {
                    "concentration": 1000,
                    "responses": [
                      1.071,
                      1.083
                    ]
                  },
                  {
                    "concentration": 1500,
                    "responses": [
                      1.45,
                      1.468
                    ]
                  },
                  {
                    "concentration": 2000,
                    "responses": [
                      1.762,
                      1.79
                    ]
                  }
                ],
                "blanks": [
                  0.052,
                  0.05
                ],
                "samples": [
                  {
                    "name": "Lysate A",
                    "responses": [
                      0.712,
                      0.705
                    ],
                    "dilutionFactor": 5
                  },
                  {
                    "name": "Lysate B",
                    "responses": [
                      0.402,
                      0.41
                    ],
                    "dilutionFactor": 5
                  },
                  {
                    "name": "Lysate C",
                    "responses": [
                      1.95,
                      1.97
                    ],
                    "dilutionFactor": 1
                  }
                ],
                "loading": {
                  "mass": 20,
                  "massUnit": "ug",
                  "finalVolume": 20
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculated result, with a notebook entry in Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProteinAssayOutput"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON."
          },
          "413": {
            "description": "The request body exceeds the allowed size."
          },
          "422": {
            "description": "The inputs could not be used; validationErrors says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProteinAssayOutput"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "MolarityCalculatorInput": {
        "title": "Molarity and Solution Prep Calculator Input",
        "description": "Input contract for the Verbex molarity and solution preparation endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "solid",
              "stock"
            ],
            "description": "Use solid to calculate mass from molecular weight, or stock for C1V1 dilution."
          },
          "solutionName": {
            "type": "string",
            "description": "Name for the solution being prepared."
          },
          "solvent": {
            "type": "string",
            "description": "Solvent or diluent to include in the preparation note."
          },
          "molecularWeight": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Molecular weight in g/mol. Required when mode is solid."
          },
          "targetConcentration": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "C2 final or desired target concentration."
          },
          "targetConcentrationUnit": {
            "type": "string",
            "enum": [
              "M",
              "mM",
              "uM",
              "nM"
            ]
          },
          "finalVolume": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "V2 final preparation volume."
          },
          "finalVolumeUnit": {
            "type": "string",
            "enum": [
              "L",
              "mL",
              "uL"
            ]
          },
          "solveFor": {
            "type": "string",
            "enum": [
              "stockVolume",
              "stockConcentration",
              "targetConcentration",
              "finalVolume"
            ],
            "description": "For stock mode, choose which C1V1 variable to solve."
          },
          "stockConcentration": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "C1 starting stock concentration."
          },
          "stockConcentrationUnit": {
            "type": "string",
            "enum": [
              "M",
              "mM",
              "uM",
              "nM"
            ]
          },
          "stockVolume": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "V1 stock volume."
          },
          "stockVolumeUnit": {
            "type": "string",
            "enum": [
              "L",
              "mL",
              "uL"
            ]
          }
        },
        "required": [
          "mode"
        ],
        "allOf": [
          {
            "if": {
              "properties": {
                "mode": {
                  "const": "solid"
                }
              },
              "required": [
                "mode"
              ]
            },
            "then": {
              "required": [
                "targetConcentration",
                "targetConcentrationUnit",
                "finalVolume",
                "finalVolumeUnit",
                "molecularWeight"
              ]
            }
          },
          {
            "if": {
              "properties": {
                "mode": {
                  "const": "stock"
                }
              },
              "required": [
                "mode"
              ]
            },
            "then": {
              "required": [
                "solveFor",
                "stockConcentrationUnit",
                "stockVolumeUnit",
                "targetConcentrationUnit",
                "finalVolumeUnit"
              ],
              "oneOf": [
                {
                  "properties": {
                    "solveFor": {
                      "const": "stockConcentration"
                    }
                  },
                  "required": [
                    "stockVolume",
                    "targetConcentration",
                    "finalVolume"
                  ]
                },
                {
                  "properties": {
                    "solveFor": {
                      "const": "stockVolume"
                    }
                  },
                  "required": [
                    "stockConcentration",
                    "targetConcentration",
                    "finalVolume"
                  ]
                },
                {
                  "properties": {
                    "solveFor": {
                      "const": "targetConcentration"
                    }
                  },
                  "required": [
                    "stockConcentration",
                    "stockVolume",
                    "finalVolume"
                  ]
                },
                {
                  "properties": {
                    "solveFor": {
                      "const": "finalVolume"
                    }
                  },
                  "required": [
                    "stockConcentration",
                    "stockVolume",
                    "targetConcentration"
                  ]
                }
              ]
            }
          }
        ]
      },
      "MolarityCalculatorOutput": {
        "title": "Molarity and Solution Prep Calculator Output",
        "description": "Response contract for the Verbex molarity and solution preparation endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "molarity-calculator"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object",
            "description": "Structured calculation object also returned as outputs.json.",
            "additionalProperties": true
          },
          "outputs": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "json": {
                "type": "object",
                "additionalProperties": true
              },
              "markdown": {
                "type": "string",
                "description": "ELN-ready Markdown note."
              },
              "html": {
                "type": "string",
                "description": "Formatted HTML note for rich-text editors."
              }
            },
            "required": [
              "json",
              "markdown",
              "html"
            ]
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "manifest": {
                "type": "string",
                "format": "uri"
              },
              "inputSchema": {
                "type": "string",
                "format": "uri"
              },
              "outputSchema": {
                "type": "string",
                "format": "uri"
              },
              "exampleRequest": {
                "type": "string",
                "format": "uri"
              },
              "exampleResponse": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "human",
              "manifest",
              "inputSchema",
              "outputSchema"
            ]
          },
          "privacy": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "accountRequired": {
                "type": "boolean"
              },
              "storesInput": {
                "type": "boolean"
              },
              "cloudAiUsed": {
                "type": "boolean"
              },
              "note": {
                "type": "string"
              }
            },
            "required": [
              "accountRequired",
              "storesInput",
              "cloudAiUsed",
              "note"
            ]
          }
        },
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "generatedAt",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ]
      },
      "SerialDilutionCalculatorInput": {
        "title": "Serial Dilution Protocol Planner Input",
        "description": "Input contract for the Verbex serial dilution calculator endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "seriesName": {
            "type": "string",
            "description": "Name for the dilution series."
          },
          "stockConcentration": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Starting stock concentration before the first dilution."
          },
          "concentrationUnit": {
            "type": "string",
            "enum": [
              "M",
              "mM",
              "uM",
              "nM",
              "pM",
              "mg/mL",
              "ug/mL",
              "ng/uL",
              "ng/mL",
              "x"
            ],
            "description": "Unit for stock and calculated final concentrations."
          },
          "dilutionFactor": {
            "type": "number",
            "exclusiveMinimum": 1,
            "description": "Constant dilution factor applied at each dilution step."
          },
          "steps": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "Number of dilution tubes to generate."
          },
          "finalVolume": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Final volume in each tube."
          },
          "volumeUnit": {
            "type": "string",
            "enum": [
              "uL",
              "mL"
            ],
            "description": "Volume unit for transfer, diluent, and final tube volumes."
          }
        },
        "required": [
          "stockConcentration",
          "concentrationUnit",
          "dilutionFactor",
          "steps",
          "finalVolume",
          "volumeUnit"
        ]
      },
      "SerialDilutionCalculatorOutput": {
        "title": "Serial Dilution Protocol Planner Output",
        "description": "Response contract for the Verbex serial dilution calculator endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "serial-dilution-calculator"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object",
            "description": "Structured dilution object also returned as outputs.json.",
            "additionalProperties": true
          },
          "outputs": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "json": {
                "type": "object",
                "additionalProperties": true
              },
              "markdown": {
                "type": "string",
                "description": "ELN-ready Markdown dilution plan."
              },
              "html": {
                "type": "string",
                "description": "Formatted HTML dilution plan for rich-text editors."
              },
              "csv": {
                "type": "string",
                "description": "CSV dilution table."
              }
            },
            "required": [
              "json",
              "markdown",
              "html",
              "csv"
            ]
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "manifest": {
                "type": "string",
                "format": "uri"
              },
              "inputSchema": {
                "type": "string",
                "format": "uri"
              },
              "outputSchema": {
                "type": "string",
                "format": "uri"
              },
              "exampleRequest": {
                "type": "string",
                "format": "uri"
              },
              "exampleResponse": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "human",
              "manifest",
              "inputSchema",
              "outputSchema"
            ]
          },
          "privacy": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "accountRequired": {
                "type": "boolean"
              },
              "storesInput": {
                "type": "boolean"
              },
              "cloudAiUsed": {
                "type": "boolean"
              },
              "note": {
                "type": "string"
              }
            },
            "required": [
              "accountRequired",
              "storesInput",
              "cloudAiUsed",
              "note"
            ]
          }
        },
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "generatedAt",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ]
      },
      "PcrMasterMixCalculatorInput": {
        "title": "PCR Master Mix Calculator Input",
        "description": "Input contract for the Verbex PCR and qPCR master mix calculator. Water is calculated automatically to fill each reaction.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "reactionName": {
            "type": "string",
            "description": "Name for the master mix, e.g. 'IL-6 qPCR plate 2'."
          },
          "reactions": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "Number of reactions (wells), including controls."
          },
          "reactionVolume": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Total volume of one reaction, including template."
          },
          "reactionVolumeUnit": {
            "type": "string",
            "enum": [
              "uL",
              "mL",
              "nL"
            ],
            "default": "uL"
          },
          "excessPercent": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 10,
            "description": "Extra master mix to cover pipetting loss, as a percent of the reactions."
          },
          "templateVolume": {
            "type": "number",
            "minimum": 0,
            "default": 0,
            "description": "Template volume added to each well separately (not in the master mix)."
          },
          "templateVolumeUnit": {
            "type": "string",
            "enum": [
              "uL",
              "mL",
              "nL"
            ],
            "description": "Defaults to the reaction volume unit."
          },
          "components": {
            "type": "array",
            "minItems": 1,
            "description": "Master mix components except water (water fills the remaining volume). Give each either a stock and final concentration (or amount per reaction), or a fixed volume per reaction.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "name"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Component name, e.g. '2x SYBR Green mix', 'Forward primer', 'Taq polymerase'."
                },
                "stockConcentration": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "Concentration of the stock tube."
                },
                "stockUnit": {
                  "type": "string",
                  "enum": [
                    "M",
                    "mM",
                    "uM",
                    "nM",
                    "pM",
                    "g/L",
                    "mg/mL",
                    "ug/uL",
                    "ug/mL",
                    "ng/uL",
                    "ng/mL",
                    "U/uL",
                    "U/mL",
                    "x",
                    "%"
                  ],
                  "description": "Stock unit: molar, mass per volume, U/µL for enzymes, x for fold buffers, % for percent."
                },
                "finalConcentration": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "Final concentration in the reaction (same kind of unit as the stock), or an amount per reaction (U, ng, pmol) when the stock is a concentration."
                },
                "finalUnit": {
                  "type": "string",
                  "enum": [
                    "M",
                    "mM",
                    "uM",
                    "nM",
                    "pM",
                    "g/L",
                    "mg/mL",
                    "ug/uL",
                    "ug/mL",
                    "ng/uL",
                    "ng/mL",
                    "U/uL",
                    "U/mL",
                    "x",
                    "%",
                    "mol",
                    "mmol",
                    "umol",
                    "nmol",
                    "pmol",
                    "g",
                    "mg",
                    "ug",
                    "ng",
                    "pg",
                    "U"
                  ]
                },
                "volumePerReaction": {
                  "type": "number",
                  "minimum": 0,
                  "description": "Use instead of stock/final to add a fixed volume per reaction."
                },
                "volumeUnit": {
                  "type": "string",
                  "enum": [
                    "uL",
                    "mL",
                    "nL"
                  ],
                  "default": "uL"
                }
              }
            }
          }
        },
        "required": [
          "reactions",
          "reactionVolume",
          "components"
        ]
      },
      "PcrMasterMixCalculatorOutput": {
        "title": "PCR Master Mix Calculator Output",
        "description": "Response envelope shared by Verbex Lab Tools endpoints. outputs.markdown is a notebook-ready entry with a built-in check.",
        "type": "object",
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ],
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "pcr-master-mix-calculator"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object"
          },
          "outputs": {
            "type": "object",
            "properties": {
              "json": {
                "type": "object"
              },
              "markdown": {
                "type": "string"
              }
            }
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object"
          },
          "privacy": {
            "type": "object"
          }
        }
      },
      "LnpFormulationCalculatorInput": {
        "title": "LNP Formulation Calculator Input",
        "description": "Input contract for the Verbex lipid nanoparticle (LNP) formulation calculator: lipid amounts from RNA mass, N/P ratio and lipid mol%, and the ethanol and aqueous phase volumes for mixing.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "formulationName": {
            "type": "string"
          },
          "rnaMass": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Mass of RNA (mRNA, siRNA, sgRNA) to encapsulate."
          },
          "rnaMassUnit": {
            "type": "string",
            "enum": [
              "ug",
              "mg",
              "ng",
              "g"
            ],
            "default": "ug"
          },
          "rnaStockConcentration": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Concentration of the RNA stock."
          },
          "rnaStockUnit": {
            "type": "string",
            "enum": [
              "mg/mL",
              "ug/uL",
              "ug/mL",
              "ng/uL"
            ],
            "default": "mg/mL"
          },
          "npRatio": {
            "type": "number",
            "exclusiveMinimum": 0,
            "default": 6,
            "description": "Molar ratio of ionizable-lipid amines (N) to RNA phosphates (P)."
          },
          "nucleotideMolecularWeight": {
            "type": "number",
            "exclusiveMinimum": 0,
            "default": 330,
            "description": "Average mass per nucleotide used to estimate RNA phosphate (g/mol)."
          },
          "aqueousToEthanolRatio": {
            "type": "number",
            "exclusiveMinimum": 0,
            "default": 3,
            "description": "Volume ratio of aqueous (RNA) phase to ethanol (lipid) phase at mixing; 3 means 3:1."
          },
          "aqueousVolume": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Volume of the aqueous RNA phase."
          },
          "aqueousVolumeUnit": {
            "type": "string",
            "enum": [
              "uL",
              "mL",
              "L"
            ],
            "default": "uL"
          },
          "lipids": {
            "type": "array",
            "minItems": 2,
            "description": "Every lipid in the formulation; mol% must add up to 100 and exactly one is the ionizable lipid.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "name",
                "molPercent",
                "molecularWeight",
                "stockConcentration"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "e.g. 'SM-102', 'DSPC', 'Cholesterol', 'DMG-PEG2000'."
                },
                "molPercent": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "maximum": 100
                },
                "molecularWeight": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "g/mol, from the certificate of analysis (PEG-lipids are averages)."
                },
                "stockConcentration": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "Concentration of the lipid stock in ethanol."
                },
                "stockUnit": {
                  "type": "string",
                  "enum": [
                    "mg/mL",
                    "mM"
                  ],
                  "default": "mg/mL"
                },
                "ionizable": {
                  "type": "boolean",
                  "default": false,
                  "description": "True for the ionizable (cationic) lipid that sets the N/P ratio."
                },
                "aminesPerMolecule": {
                  "type": "integer",
                  "minimum": 1,
                  "default": 1,
                  "description": "Ionizable amines per molecule of the ionizable lipid."
                }
              }
            }
          }
        },
        "required": [
          "rnaMass",
          "rnaStockConcentration",
          "aqueousVolume",
          "lipids"
        ]
      },
      "LnpFormulationCalculatorOutput": {
        "title": "LNP Formulation Calculator Output",
        "description": "Response envelope shared by Verbex Lab Tools endpoints. outputs.markdown is a notebook-ready entry with a built-in check.",
        "type": "object",
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ],
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "lnp-formulation-calculator"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object"
          },
          "outputs": {
            "type": "object",
            "properties": {
              "json": {
                "type": "object"
              },
              "markdown": {
                "type": "string"
              }
            }
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object"
          },
          "privacy": {
            "type": "object"
          }
        }
      },
      "Tool96WellPlateMapBuilderInput": {
        "title": "Plate Map Builder Input",
        "description": "Input contract for the Verbex plate map builder: lays out samples, standards, controls and blanks with replicates on 6- to 384-well plates.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "plateName": {
            "type": "string"
          },
          "format": {
            "type": "integer",
            "enum": [
              6,
              12,
              24,
              48,
              96,
              384
            ],
            "default": 96
          },
          "samples": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Unique sample names, in placement order."
          },
          "replicates": {
            "type": "integer",
            "minimum": 1,
            "maximum": 24,
            "default": 3,
            "description": "Replicates per sample."
          },
          "standards": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "points"
            ],
            "description": "Optional standard curve, placed first.",
            "properties": {
              "name": {
                "type": "string",
                "default": "Std",
                "description": "Short prefix, e.g. 'Std'."
              },
              "points": {
                "type": "integer",
                "minimum": 1,
                "maximum": 24
              },
              "replicates": {
                "type": "integer",
                "minimum": 1,
                "maximum": 24
              }
            }
          },
          "controls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Control names (e.g. 'NTC', 'Positive control'), placed after samples."
          },
          "controlReplicates": {
            "type": "integer",
            "minimum": 1,
            "maximum": 24,
            "description": "Defaults to the sample replicates."
          },
          "blanks": {
            "type": "integer",
            "minimum": 0,
            "default": 0,
            "description": "Number of blank wells, placed last."
          },
          "fillDirection": {
            "type": "string",
            "enum": [
              "row",
              "column"
            ],
            "default": "row",
            "description": "Fill across rows (A1, A2, …) or down columns (A1, B1, …)."
          },
          "avoidEdges": {
            "type": "boolean",
            "default": false,
            "description": "Leave the outer wells empty to reduce edge effects."
          },
          "keepReplicatesTogether": {
            "type": "boolean",
            "default": true,
            "description": "Start a new row or column instead of splitting a replicate set."
          }
        }
      },
      "Tool96WellPlateMapBuilderOutput": {
        "title": "Plate Map Builder Output",
        "description": "Response envelope shared by Verbex Lab Tools endpoints. outputs.markdown is a notebook-ready entry with a built-in check.",
        "type": "object",
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ],
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "96-well-plate-map-builder"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object"
          },
          "outputs": {
            "type": "object",
            "properties": {
              "json": {
                "type": "object"
              },
              "markdown": {
                "type": "string"
              }
            }
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object"
          },
          "privacy": {
            "type": "object"
          }
        }
      },
      "LabProtocolTemplateBuilderSectionItems": {
        "type": "array",
        "description": "Ordered protocol sections to include. Library sections use source=library and an existing id. Custom sections use source=custom with a title.",
        "items": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "id": {
              "type": "string"
            },
            "source": {
              "type": "string",
              "enum": [
                "library",
                "custom"
              ]
            },
            "title": {
              "type": "string"
            },
            "description": {
              "type": "string"
            }
          },
          "required": [
            "id",
            "source"
          ]
        }
      },
      "LabProtocolTemplateBuilderInput": {
        "title": "Lab Protocol Template Builder Input",
        "description": "Input contract for the Verbex lab protocol template builder endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "protocolTitle": {
            "type": "string",
            "description": "Title for the protocol scaffold."
          },
          "presetId": {
            "type": "string",
            "enum": [
              "general",
              "elisa",
              "westernBlot",
              "qpcr",
              "cellCulture"
            ],
            "description": "Protocol scaffold preset to use."
          },
          "preset": {
            "type": "string",
            "enum": [
              "general",
              "elisa",
              "westernBlot",
              "qpcr",
              "cellCulture"
            ],
            "description": "Alias for presetId."
          },
          "includeExecutionNotes": {
            "type": "boolean",
            "description": "Whether to include ELN execution notes for running the protocol."
          },
          "includeVersionHistory": {
            "type": "boolean",
            "description": "Whether to include protocol version history when present in sectionItems."
          },
          "sectionItems": {
            "$ref": "#/components/schemas/LabProtocolTemplateBuilderSectionItems"
          },
          "sections": {
            "$ref": "#/components/schemas/LabProtocolTemplateBuilderSectionItems",
            "description": "Alias for sectionItems."
          }
        }
      },
      "LabProtocolTemplateBuilderOutput": {
        "title": "Lab Protocol Template Builder Output",
        "description": "Response contract for the Verbex lab protocol template builder endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "lab-protocol-template-builder"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "tool": {
                "const": "lab-protocol-template-builder"
              },
              "protocolTitle": {
                "type": "string"
              },
              "sections": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "safetyNotes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "outputs": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "json": {
                "type": "object",
                "additionalProperties": true
              },
              "markdown": {
                "type": "string"
              },
              "html": {
                "type": "string"
              }
            },
            "required": [
              "json",
              "markdown",
              "html"
            ]
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": true
          },
          "privacy": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "accountRequired": {
                "type": "boolean"
              },
              "storesInput": {
                "type": "boolean"
              },
              "cloudAiUsed": {
                "type": "boolean"
              },
              "note": {
                "type": "string"
              }
            },
            "required": [
              "accountRequired",
              "storesInput",
              "cloudAiUsed",
              "note"
            ]
          }
        },
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "generatedAt",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ]
      },
      "ElnTemplateBuilderSectionItems": {
        "type": "array",
        "description": "Ordered ELN sections to include. Library sections use source=library and an existing id. Custom sections use source=custom with a title.",
        "items": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "id": {
              "type": "string"
            },
            "source": {
              "type": "string",
              "enum": [
                "library",
                "custom"
              ]
            },
            "title": {
              "type": "string"
            },
            "description": {
              "type": "string"
            }
          },
          "required": [
            "id",
            "source"
          ]
        }
      },
      "ElnTemplateBuilderInput": {
        "title": "ELN Template Builder Input",
        "description": "Input contract for the Verbex ELN template builder endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "experimentTitle": {
            "type": "string",
            "description": "Title for the ELN entry template."
          },
          "presetId": {
            "type": "string",
            "enum": [
              "general",
              "cellCulture",
              "molecularBiology",
              "chemistry"
            ],
            "description": "ELN template preset to use."
          },
          "preset": {
            "type": "string",
            "enum": [
              "general",
              "cellCulture",
              "molecularBiology",
              "chemistry"
            ],
            "description": "Alias for presetId."
          },
          "includeTimestamps": {
            "type": "boolean",
            "description": "Whether to include record metadata fields."
          },
          "includeVoicePrompts": {
            "type": "boolean",
            "description": "Whether to include Verbex-style section voice prompts."
          },
          "sectionItems": {
            "$ref": "#/components/schemas/ElnTemplateBuilderSectionItems"
          },
          "sections": {
            "$ref": "#/components/schemas/ElnTemplateBuilderSectionItems",
            "description": "Alias for sectionItems."
          }
        }
      },
      "ElnTemplateBuilderOutput": {
        "title": "ELN Template Builder Output",
        "description": "Response contract for the Verbex ELN template builder endpoint.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "eln-template-builder"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "tool": {
                "const": "eln-template-builder"
              },
              "experimentTitle": {
                "type": "string"
              },
              "sections": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "safetyNotes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "outputs": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "json": {
                "type": "object",
                "additionalProperties": true
              },
              "markdown": {
                "type": "string"
              },
              "html": {
                "type": "string"
              }
            },
            "required": [
              "json",
              "markdown",
              "html"
            ]
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": true
          },
          "privacy": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "accountRequired": {
                "type": "boolean"
              },
              "storesInput": {
                "type": "boolean"
              },
              "cloudAiUsed": {
                "type": "boolean"
              },
              "note": {
                "type": "string"
              }
            },
            "required": [
              "accountRequired",
              "storesInput",
              "cloudAiUsed",
              "note"
            ]
          }
        },
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "generatedAt",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ]
      },
      "ReagentLabelScannerInput": {
        "title": "Reagent Label Scanner Input",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "ocrText": {
            "type": "string",
            "description": "OCR text extracted from a reagent label image. The browser tool performs OCR locally; this endpoint parses provided text only."
          },
          "sourceImageName": {
            "type": "string",
            "description": "Optional source image file name or identifier for review context."
          },
          "ocrConfidence": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Optional OCR confidence percentage from the OCR engine."
          }
        },
        "required": [
          "ocrText"
        ]
      },
      "ReagentLabelScannerOutput": {
        "title": "Reagent Label Scanner Output",
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "reagent-label-scanner"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "properties": {
              "tool": {
                "const": "reagent-label-scanner"
              },
              "version": {
                "type": "string"
              },
              "sourceImageName": {
                "type": "string"
              },
              "ocrConfidence": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "rawText": {
                "type": "string"
              },
              "fieldRows": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "confidence": {
                      "type": "string"
                    },
                    "needsReview": {
                      "type": "boolean"
                    },
                    "sourceText": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "key",
                    "label",
                    "value",
                    "confidence",
                    "needsReview"
                  ]
                }
              },
              "tableColumns": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "tableRows": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "validationErrors": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "reviewFlags": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "safetyNotes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "tool",
              "version",
              "fieldRows",
              "tableColumns",
              "tableRows",
              "validationErrors",
              "reviewFlags",
              "warnings",
              "safetyNotes"
            ]
          },
          "outputs": {
            "type": "object",
            "properties": {
              "json": {
                "type": "object"
              },
              "markdown": {
                "type": "string"
              },
              "html": {
                "type": "string"
              },
              "csv": {
                "type": "string"
              }
            },
            "required": [
              "json",
              "markdown",
              "html",
              "csv"
            ]
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "generatedAt",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes"
        ]
      },
      "StandardCurveInput": {
        "title": "Standard Curve Input",
        "description": "Input contract for the Verbex standard curve tool (beta): ELISA and other sigmoidal immunoassays. Fits a 4PL or 5PL curve to the standards, back-calculates every standard, sets the working range and reports samples only inside it. Default limits follow ICH M10 and can be changed per run.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "concentrationUnit",
          "standards"
        ],
        "properties": {
          "assayName": {
            "type": "string",
            "description": "Name for the run, e.g. 'IL-6 ELISA plate 1'."
          },
          "concentrationUnit": {
            "type": "string",
            "description": "Unit of the standard concentrations, e.g. 'pg/mL'. Every reported concentration uses it."
          },
          "standards": {
            "type": "array",
            "minItems": 5,
            "description": "One entry per standard level, each with all its replicate readings. At least 5 levels; the default limits need 6 passing, so 7-8 are recommended.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "concentration",
                "responses"
              ],
              "properties": {
                "concentration": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "Nominal concentration. Put zero-concentration wells under blanks."
                },
                "responses": {
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "number"
                  },
                  "description": "Raw readings (OD, RFU or RLU) of each replicate well.",
                  "maxItems": 32
                },
                "anchor": {
                  "type": "boolean",
                  "default": false,
                  "description": "True for an anchor point outside the quantifiable range: used in the fit, not judged against limits."
                }
              }
            },
            "maxItems": 64
          },
          "blanks": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Raw readings of blank (zero analyte) wells.",
            "maxItems": 1536
          },
          "samples": {
            "type": "array",
            "description": "Unknowns. The same name at several dilution factors is checked for agreement.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "name",
                "responses"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "responses": {
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "number"
                  },
                  "maxItems": 32
                },
                "dilutionFactor": {
                  "type": "number",
                  "minimum": 1,
                  "default": 1,
                  "description": "10 for a 1:10 dilution. The result is multiplied by it."
                }
              }
            },
            "maxItems": 1536
          },
          "model": {
            "type": "string",
            "enum": [
              "auto",
              "4pl",
              "5pl"
            ],
            "default": "auto",
            "description": "Auto uses 4PL unless at least 7 levels and an F test (p < 0.05) favour 5PL. The 5PL bends the lower plateau, as in GraphPad Prism and R drc."
          },
          "weighting": {
            "type": "string",
            "enum": [
              "1/y2",
              "1/y",
              "none"
            ],
            "default": "1/y2",
            "description": "Weights from the fitted response; 1/Y² suits most immunoassays. GraphPad Prism, MedCalc, R and most Excel templates fit unweighted (none) by default."
          },
          "blankHandling": {
            "type": "string",
            "enum": [
              "auto",
              "subtract",
              "subtract-and-zero",
              "zero-standard"
            ],
            "default": "auto",
            "description": "Auto subtracts blanks as background on a rising (sandwich) curve and fits them as the zero standard on a falling (competitive) curve, where zero analyte gives the strongest signal. Choose subtract or zero-standard to override."
          },
          "limits": {
            "type": "object",
            "additionalProperties": false,
            "description": "Override any default limit. Every result states the limits it used and marks changed ones.",
            "properties": {
              "recoveryPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 20
              },
              "recoveryAtEndsPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 25
              },
              "minPassingFraction": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 1,
                "default": 0.75
              },
              "minPassingLevels": {
                "type": "integer",
                "minimum": 2,
                "default": 6
              },
              "replicateCvPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 20
              },
              "replicateCvAtEndsPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 25
              },
              "dilutionAgreementPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 20
              },
              "dilutionCvPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 20
              }
            }
          },
          "exclusions": {
            "type": "array",
            "description": "Standards or single replicate wells to leave out. A reason is required; nothing is excluded without one.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "concentration",
                "reason"
              ],
              "properties": {
                "concentration": {
                  "type": "number",
                  "description": "The standard level."
                },
                "replicate": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "1-based replicate to exclude; omit to exclude the whole level."
                },
                "reason": {
                  "type": "string",
                  "minLength": 1
                }
              }
            },
            "maxItems": 128
          },
          "saturation": {
            "type": "number",
            "description": "Highest reading the instrument reports; wells at or above it are flagged."
          }
        }
      },
      "StandardCurveOutput": {
        "title": "Standard Curve Output",
        "description": "Response envelope shared by Verbex Lab Tools endpoints. result.calculated holds the verdict, fit, back-calculated standards, working range and samples; result.limits lists every limit applied, marked default or changed; result.assumptions states the method choices. outputs.markdown is the notebook-ready entry.",
        "type": "object",
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ],
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "standard-curve"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object",
            "properties": {
              "limits": {
                "type": "array",
                "description": "Every limit applied: key, value, default, changed, text.",
                "items": {
                  "type": "object"
                }
              },
              "assumptions": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "The method choices behind this result."
              },
              "flags": {
                "type": "array",
                "description": "Warnings with a code (e.g. above_range, standard_failed, hook_effect) and a plain-language message.",
                "items": {
                  "type": "object"
                }
              },
              "calculated": {
                "type": "object",
                "properties": {
                  "verdict": {
                    "type": "object",
                    "description": "passes, passing, judged and a one-line text."
                  },
                  "fit": {
                    "type": "object",
                    "description": "model, weighting, params a b c d g, rSquared, standardError, modelChoice."
                  },
                  "standards": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Per level: meanResponse, cvPercent, backCalculated, recoveryPercent, status (pass, fail, anchor, excluded)."
                  },
                  "workingRange": {
                    "type": "object",
                    "description": "lloq, uloq and unit."
                  },
                  "samples": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Per sample and dilution: concentration, finalConcentration, status (in_range, below_range, above_range, no_range) and reportable."
                  },
                  "sampleSummaries": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Per sample name: mean of in-range dilutions and whether dilutions agree."
                  },
                  "suggestions": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "What excluding each failing standard would change. Never applied automatically."
                  },
                  "check": {
                    "type": "object",
                    "description": "Self-check: standards pushed back through the fitted curve."
                  }
                }
              }
            }
          },
          "outputs": {
            "type": "object",
            "properties": {
              "json": {
                "type": "object"
              },
              "markdown": {
                "type": "string"
              }
            }
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object"
          },
          "privacy": {
            "type": "object"
          }
        }
      },
      "ProteinAssayInput": {
        "title": "Protein Assay Input",
        "description": "Input contract for the Verbex protein assay tool (beta): BCA and Bradford. Subtracts the blank, fits a quadratic (or linear or 4PL) standard curve, back-calculates every standard, reports sample concentrations within the working range, and optionally the volume to load for a target protein mass. Default limits are Verbex protein-assay defaults (no regulatory guideline exists) and can be changed.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "concentrationUnit",
          "standards"
        ],
        "properties": {
          "assayName": {
            "type": "string",
            "description": "Name for the run, e.g. 'IL-6 ELISA plate 1'."
          },
          "concentrationUnit": {
            "type": "string",
            "description": "Unit of the standards, e.g. 'ug/mL' (µg/mL). Loading volumes need a mass concentration unit (µg/mL, mg/mL, ng/µL or µg/µL)."
          },
          "standards": {
            "type": "array",
            "minItems": 3,
            "description": "One entry per standard level (e.g. BSA), each with all its replicate readings. Quadratic needs at least 4 levels, linear 3; the default limits need 5 passing.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "concentration",
                "responses"
              ],
              "properties": {
                "concentration": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "Nominal concentration. Put zero-concentration wells under blanks."
                },
                "responses": {
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "number"
                  },
                  "description": "Raw readings (OD, RFU or RLU) of each replicate well.",
                  "maxItems": 32
                },
                "anchor": {
                  "type": "boolean",
                  "default": false,
                  "description": "True for an anchor point outside the quantifiable range: used in the fit, not judged against limits."
                }
              }
            },
            "maxItems": 64
          },
          "blanks": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Raw readings of the blank (0 µg/mL) standard; subtracted from every well.",
            "maxItems": 1536
          },
          "samples": {
            "type": "array",
            "description": "Unknowns. The same name at several dilution factors is checked for agreement.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "name",
                "responses"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "responses": {
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "number"
                  },
                  "maxItems": 32
                },
                "dilutionFactor": {
                  "type": "number",
                  "minimum": 1,
                  "default": 1,
                  "description": "10 for a 1:10 dilution. The result is multiplied by it."
                }
              }
            },
            "maxItems": 1536
          },
          "model": {
            "type": "string",
            "enum": [
              "quadratic",
              "linear",
              "4pl",
              "5pl",
              "auto"
            ],
            "default": "quadratic",
            "description": "Quadratic by default: the Pierce BCA user guide notes a quadratic or best-fit curve is more accurate than a straight line. Linear and 4PL are available."
          },
          "weighting": {
            "type": "string",
            "enum": [
              "1/y2",
              "1/y",
              "none"
            ],
            "default": "none",
            "description": "Unweighted by default, as in the kit instructions and Excel. 1/Y and 1/Y² weight the low end more."
          },
          "blankHandling": {
            "type": "string",
            "enum": [
              "auto",
              "subtract",
              "subtract-and-zero",
              "zero-standard"
            ],
            "default": "auto",
            "description": "Auto subtracts the blank from every well and keeps it in the fit as the 0 standard, as the kit instructions and instrument software do. 'subtract' leaves the zero point out; 'zero-standard' fits the raw blank as the 0 standard."
          },
          "limits": {
            "type": "object",
            "additionalProperties": false,
            "description": "Override any default limit. Defaults are Verbex choices (no regulatory guideline sets acceptance limits for protein assays); every result states the limits it used.",
            "properties": {
              "recoveryPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 15
              },
              "recoveryAtEndsPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 25
              },
              "minPassingFraction": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 1,
                "default": 0.75
              },
              "minPassingLevels": {
                "type": "integer",
                "minimum": 2,
                "default": 5
              },
              "replicateCvPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 15
              },
              "replicateCvAtEndsPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 20
              },
              "dilutionAgreementPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 20
              },
              "dilutionCvPercent": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100,
                "default": 20
              }
            }
          },
          "exclusions": {
            "type": "array",
            "description": "Standards or single replicate wells to leave out. A reason is required; nothing is excluded without one.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "concentration",
                "reason"
              ],
              "properties": {
                "concentration": {
                  "type": "number",
                  "description": "The standard level."
                },
                "replicate": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "1-based replicate to exclude; omit to exclude the whole level."
                },
                "reason": {
                  "type": "string",
                  "minLength": 1
                }
              }
            },
            "maxItems": 128
          },
          "saturation": {
            "type": "number",
            "description": "Highest reading the instrument reports; wells at or above it are flagged."
          },
          "loading": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "mass"
            ],
            "description": "Optional: volume of each sample to load for a target protein mass (e.g. 20 µg per lane), and the buffer to bring every lane to the same volume.",
            "properties": {
              "mass": {
                "type": "number",
                "exclusiveMinimum": 0
              },
              "massUnit": {
                "type": "string",
                "enum": [
                  "ng",
                  "ug",
                  "mg"
                ],
                "default": "ug"
              },
              "finalVolume": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "Volume per lane in µL; omit to get sample volumes only."
              }
            }
          }
        }
      },
      "ProteinAssayOutput": {
        "title": "Protein Assay Output",
        "description": "Response envelope shared by Verbex Lab Tools endpoints. result.calculated holds the verdict, fit, back-calculated standards, working range and samples; result.limits lists every limit applied, marked default or changed; result.assumptions states the method choices. outputs.markdown is the notebook-ready entry.",
        "type": "object",
        "required": [
          "ok",
          "tool",
          "toolVersion",
          "schemaVersion",
          "result",
          "outputs",
          "validationErrors",
          "warnings",
          "safetyNotes",
          "links",
          "privacy"
        ],
        "properties": {
          "notebookEntry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short notebook-ready Markdown entry: inputs, results to 4 significant figures and an independent Check line. Null when the request is invalid."
          },
          "ok": {
            "type": "boolean"
          },
          "tool": {
            "const": "protein-assay"
          },
          "toolVersion": {
            "type": "string"
          },
          "schemaVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": "object",
            "properties": {
              "limits": {
                "type": "array",
                "description": "Every limit applied: key, value, default, changed, text.",
                "items": {
                  "type": "object"
                }
              },
              "assumptions": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "The method choices behind this result."
              },
              "flags": {
                "type": "array",
                "description": "Warnings with a code (e.g. above_range, standard_failed, hook_effect) and a plain-language message.",
                "items": {
                  "type": "object"
                }
              },
              "calculated": {
                "type": "object",
                "properties": {
                  "verdict": {
                    "type": "object",
                    "description": "passes, passing, judged and a one-line text."
                  },
                  "fit": {
                    "type": "object",
                    "description": "model, weighting, params a b c d g, rSquared, standardError, modelChoice."
                  },
                  "standards": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Per level: meanResponse, cvPercent, backCalculated, recoveryPercent, status (pass, fail, anchor, excluded)."
                  },
                  "workingRange": {
                    "type": "object",
                    "description": "lloq, uloq and unit."
                  },
                  "samples": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Per sample and dilution: concentration, finalConcentration, status (in_range, below_range, above_range, no_range) and reportable."
                  },
                  "sampleSummaries": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Per sample name: mean of in-range dilutions and whether dilutions agree."
                  },
                  "suggestions": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "What excluding each failing standard would change. Never applied automatically."
                  },
                  "check": {
                    "type": "object",
                    "description": "Self-check: standards pushed back through the fitted curve."
                  },
                  "loading": {
                    "type": "object",
                    "description": "When loading was requested: mass, massUnit, finalVolume and per-sample rows (sampleUl, bufferUl, status)."
                  }
                }
              }
            }
          },
          "outputs": {
            "type": "object",
            "properties": {
              "json": {
                "type": "object"
              },
              "markdown": {
                "type": "string"
              }
            }
          },
          "validationErrors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyNotes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object"
          },
          "privacy": {
            "type": "object"
          }
        }
      }
    }
  }
}
