{"openapi":"3.0.0","paths":{"/v1/biomarker/status":{"get":{"operationId":"getV1Status","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"service":"biomarker-analysis-v7","ok":true}}}}},"401":{"description":"The `x-api-key` header is missing or wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"x-api-key":[]}],"summary":"Check the v1 surface","tags":["biomarker (v1)"]}},"/v1/biomarker/run":{"post":{"description":"Holds the request open for the whole run (6 to 8 minutes; with Opus 5.5 about 13) and answers with the outputs. From 30 seconds on it sends a space every 30 seconds, so a proxy that closes quiet requests keeps it open (Railway closes one after 5 minutes without data); JSON parsers skip the spaces. The 201 goes out with the first space, so a run that fails after that answers 201 with the error body: check the body for `statusCode`. Railway closes any request after 15 minutes, so use run-async with Opus 5.5. Files travel inline, so the JSON body limit of the server applies.","operationId":"runSync","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["input_0","Other_files"],"properties":{"input_0":{"type":"string","description":"The primary input: the patient's intake (age, sex, goals, symptoms, medications, history) and any question or lab text. The IP guard screens it first; a blocked input ends the run with a refusal message in both reports.","example":"Synthetic patient, 46-year-old man. Goals: heart health, energy and sleep. No medications. Father had a heart attack at 52."},"Other_files":{"type":"array","items":{"type":"object","required":["metadata","raw_bytes","type"],"properties":{"metadata":{"type":"object","required":["name","mime_type"],"properties":{"name":{"type":"string","example":"labs.pdf"},"mime_type":{"type":"string","example":"application/pdf"}}},"raw_bytes":{"type":"string","format":"byte","description":"The file bytes, base64, without a `data:` prefix."},"type":{"type":"string","example":"file"}}},"description":"Files sent inline. PDFs are read for their text (scanned PDFs through Claude vision OCR); text and CSV files are read as UTF-8. Other types are read as UTF-8 too, so send lab images only to a server with the platform fixes on.","minItems":1},"Previous_Data":{"type":"string","default":"","description":"Earlier results or a previous report, for the longitudinal comparison (the `progress` template)."},"Report_Type":{"type":"string","default":"baseline","description":"The report template, given to the writers as text. The website sends `baseline` (Comprehensive), `focused` (the top 3 to 5 findings), `progress` (an interval comparison) or `snapshot` (a quick scorecard).","example":"baseline"},"language":{"type":"string","default":"English","description":"Every word of both reports is written in this language.","example":"English"},"overall_instructions":{"type":"string","default":"","description":"The clinic's own instructions, placed first in the clinical and visual report prompts: tone, emphasis, format."},"User_Preference_Level":{"type":"string","default":"Default","description":"Detail depth: how technical the explanations are, never how aggressive the interventions are. The website sends a directive that starts `DETAIL DEPTH: DEFAULT` or `DETAIL DEPTH: DETAILED`."},"intervention_philosophy":{"type":"string","enum":["conservative","balanced","progressive","aggressive"],"default":"balanced","description":"How far the recommendations go: lifestyle only, plus well-established supplements and standard medications, plus peptides and hormones, or full advanced protocols. Unknown values run as `balanced`."},"design":{"type":"string","enum":["classic","modern"],"default":"classic","description":"The visual report's design kit: `classic` (Cormorant Garamond, warm taupe) or `modern` (Zen Old Mincho and Familjen Grotesk, slate). Unknown values run as `classic`."},"protocols_active":{"type":"string","enum":["true","false"],"default":"false","description":"`\"true\"` turns on stage 9, protocol reconciliation. Anything else skips it and the draft report stands."},"protocols_store":{"type":"string","default":"","description":"The physician's protocol library, a Gemini File Search store, searched with a query built from the draft report.","example":"fileSearchStores/your-protocols-store"},"protocols_thisrun":{"type":"string","default":"","description":"A one-time physician directive for this run, applied verbatim with the highest priority."},"writer_model":{"type":"string","enum":["claude-opus-4-8","claude-opus-5-5","claude-sonnet-5-5"],"default":"claude-opus-4-8","description":"The Claude model that writes the report: stage 8 (clinical report), stage 9 (protocol reconciliation) and stage 11 (visual report). Other values are refused with 400."}},"example":{"input_0":"Synthetic patient, 46-year-old man. Goals: heart health, energy and sleep. No medications. Father had a heart attack at 52.","Other_files":[{"metadata":{"name":"labs.txt","mime_type":"text/plain"},"raw_bytes":"TERMLUM6IDE0MiBtZy9kTCAocmVmIDwxMDApCkhiQTFjOiA1LjYgJQo=","type":"file"}],"Report_Type":"baseline","language":"English","intervention_philosophy":"balanced"}}}}},"responses":{"201":{"description":"The outputs of the run. A run that fails after its first 30 seconds also answers 201, with the error body (`statusCode`, `error`, `message`) in place of the outputs.","content":{"application/json":{"schema":{"type":"object","properties":{"Visual_Report":{"type":"string","description":"The visual report, a complete HTML page. When the IP guard blocks the input: the refusal message."},"output_0":{"type":"string","description":"The clinical report in Markdown, ending with its REFERENCES section (papers looked up in Europe PMC). When blocked: the refusal message."},"gap_analysis":{"type":"string","description":"Stage 6: the knowledge base coverage gaps and the targeted follow-up query."},"processed_texts":{"type":"string","description":"The text read from each file, as `<document name=\"...\">` blocks."},"credits":{"type":"object","properties":{"report":{"type":"string"},"visual":{"type":"string"}},"description":"Credits of the clinical and visual reports, as text. The token counts of every call go to the telemetry table instead."},"db_write_status":{"type":"string","description":"The telemetry write, as JSON text: `success`, `error`, or `skipped` when DATABASE_URL is not set."},"checks":{"type":"object","description":"Only with PLATFORM_FIXES=on: what the checks found in the first visual report, and whether its one rewrite replaced it.","properties":{"version":{"type":"string"},"codes":{"type":"array","items":{"type":"string","enum":["value_mismatch","target_unit_mismatch","timing_conflict","context_conflict"]}},"rewritten":{"type":"boolean"}}},"truncated":{"type":"array","items":{"type":"string","enum":["output_0","Visual_Report"]},"description":"Only when it happened: the outputs the model stopped at its output limit, so their end is missing. A protocol rewrite that stops there is dropped for the complete draft instead."}}}}}},"400":{"description":"A field is missing or invalid, or `Other_files` is empty.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"401":{"description":"The `x-api-key` header is missing or wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"413":{"description":"The body is over the server's 100 KB JSON limit. For larger files use POST /api/pipeline/start with `file_keys`.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"500":{"description":"The run failed in its first 30 seconds. `message` names the error of the stage that stopped it.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"x-api-key":[]}],"summary":"Run and wait for the report","tags":["biomarker (v1)"]}},"/v1/biomarker/run-async":{"post":{"description":"The same body as POST /v1/biomarker/run, answered at once with a `runId`. Poll GET /v1/biomarker/runs/{id}; these runs report their status but not their stage.","operationId":"runAsync","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["input_0","Other_files"],"properties":{"input_0":{"type":"string","description":"The primary input: the patient's intake (age, sex, goals, symptoms, medications, history) and any question or lab text. The IP guard screens it first; a blocked input ends the run with a refusal message in both reports.","example":"Synthetic patient, 46-year-old man. Goals: heart health, energy and sleep. No medications. Father had a heart attack at 52."},"Other_files":{"type":"array","items":{"type":"object","required":["metadata","raw_bytes","type"],"properties":{"metadata":{"type":"object","required":["name","mime_type"],"properties":{"name":{"type":"string","example":"labs.pdf"},"mime_type":{"type":"string","example":"application/pdf"}}},"raw_bytes":{"type":"string","format":"byte","description":"The file bytes, base64, without a `data:` prefix."},"type":{"type":"string","example":"file"}}},"description":"Files sent inline. PDFs are read for their text (scanned PDFs through Claude vision OCR); text and CSV files are read as UTF-8. Other types are read as UTF-8 too, so send lab images only to a server with the platform fixes on.","minItems":1},"Previous_Data":{"type":"string","default":"","description":"Earlier results or a previous report, for the longitudinal comparison (the `progress` template)."},"Report_Type":{"type":"string","default":"baseline","description":"The report template, given to the writers as text. The website sends `baseline` (Comprehensive), `focused` (the top 3 to 5 findings), `progress` (an interval comparison) or `snapshot` (a quick scorecard).","example":"baseline"},"language":{"type":"string","default":"English","description":"Every word of both reports is written in this language.","example":"English"},"overall_instructions":{"type":"string","default":"","description":"The clinic's own instructions, placed first in the clinical and visual report prompts: tone, emphasis, format."},"User_Preference_Level":{"type":"string","default":"Default","description":"Detail depth: how technical the explanations are, never how aggressive the interventions are. The website sends a directive that starts `DETAIL DEPTH: DEFAULT` or `DETAIL DEPTH: DETAILED`."},"intervention_philosophy":{"type":"string","enum":["conservative","balanced","progressive","aggressive"],"default":"balanced","description":"How far the recommendations go: lifestyle only, plus well-established supplements and standard medications, plus peptides and hormones, or full advanced protocols. Unknown values run as `balanced`."},"design":{"type":"string","enum":["classic","modern"],"default":"classic","description":"The visual report's design kit: `classic` (Cormorant Garamond, warm taupe) or `modern` (Zen Old Mincho and Familjen Grotesk, slate). Unknown values run as `classic`."},"protocols_active":{"type":"string","enum":["true","false"],"default":"false","description":"`\"true\"` turns on stage 9, protocol reconciliation. Anything else skips it and the draft report stands."},"protocols_store":{"type":"string","default":"","description":"The physician's protocol library, a Gemini File Search store, searched with a query built from the draft report.","example":"fileSearchStores/your-protocols-store"},"protocols_thisrun":{"type":"string","default":"","description":"A one-time physician directive for this run, applied verbatim with the highest priority."},"writer_model":{"type":"string","enum":["claude-opus-4-8","claude-opus-5-5","claude-sonnet-5-5"],"default":"claude-opus-4-8","description":"The Claude model that writes the report: stage 8 (clinical report), stage 9 (protocol reconciliation) and stage 11 (visual report). Other values are refused with 400."}},"example":{"input_0":"Synthetic patient, 46-year-old man. Goals: heart health, energy and sleep. No medications. Father had a heart attack at 52.","Other_files":[{"metadata":{"name":"labs.txt","mime_type":"text/plain"},"raw_bytes":"TERMLUM6IDE0MiBtZy9kTCAocmVmIDwxMDApCkhiQTFjOiA1LjYgJQo=","type":"file"}],"Report_Type":"baseline","language":"English","intervention_philosophy":"balanced"}}}}},"responses":{"201":{"description":"The run started.","content":{"application/json":{"schema":{"type":"object","example":{"runId":"9c0e2d4b-3f1a-4b8e-a6d2-5e7f9a1c3b24","status":"running"}}}}},"400":{"description":"A field is missing or invalid, or `Other_files` is empty.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"401":{"description":"The `x-api-key` header is missing or wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"413":{"description":"The body is over the server's 100 KB JSON limit. For larger files use POST /api/pipeline/start with `file_keys`.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"x-api-key":[]}],"summary":"Start a run","tags":["biomarker (v1)"]}},"/v1/biomarker/runs/{id}":{"get":{"operationId":"getV1Run","parameters":[{"name":"id","required":true,"in":"path","description":"The `runId` from POST /v1/biomarker/run-async.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","running","completed","failed"]},"createdAt":{"type":"integer","description":"Unix time in milliseconds."},"updatedAt":{"type":"integer","description":"Unix time in milliseconds."},"result":{"type":"object","properties":{"Visual_Report":{"type":"string","description":"The visual report, a complete HTML page. When the IP guard blocks the input: the refusal message."},"output_0":{"type":"string","description":"The clinical report in Markdown, ending with its REFERENCES section (papers looked up in Europe PMC). When blocked: the refusal message."},"gap_analysis":{"type":"string","description":"Stage 6: the knowledge base coverage gaps and the targeted follow-up query."},"processed_texts":{"type":"string","description":"The text read from each file, as `<document name=\"...\">` blocks."},"credits":{"type":"object","properties":{"report":{"type":"string"},"visual":{"type":"string"}},"description":"Credits of the clinical and visual reports, as text. The token counts of every call go to the telemetry table instead."},"db_write_status":{"type":"string","description":"The telemetry write, as JSON text: `success`, `error`, or `skipped` when DATABASE_URL is not set."},"checks":{"type":"object","description":"Only with PLATFORM_FIXES=on: what the checks found in the first visual report, and whether its one rewrite replaced it.","properties":{"version":{"type":"string"},"codes":{"type":"array","items":{"type":"string","enum":["value_mismatch","target_unit_mismatch","timing_conflict","context_conflict"]}},"rewritten":{"type":"boolean"}}},"truncated":{"type":"array","items":{"type":"string","enum":["output_0","Visual_Report"]},"description":"Only when it happened: the outputs the model stopped at its output limit, so their end is missing. A protocol rewrite that stops there is dropped for the complete draft instead."}},"description":"Present once `status` is `completed`."},"error":{"type":"string","description":"Present when `status` is `failed`."}},"example":{"id":"9c0e2d4b-3f1a-4b8e-a6d2-5e7f9a1c3b24","status":"running","createdAt":1792771200000,"updatedAt":1792771200000}}}}},"401":{"description":"The `x-api-key` header is missing or wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"404":{"description":"This server does not know the id (it keeps the last 200 runs, in memory).","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"x-api-key":[]}],"summary":"Poll a v1 run","tags":["biomarker (v1)"]}},"/api/pipeline/start":{"post":{"description":"Starts the 12-stage pipeline and answers at once with a `taskId`. Poll GET /api/pipeline/status/{taskId} for its progress and result. A run takes 6 to 8 minutes (Opus 5.5: about 13) and stops at BIOMARKER_DEADLINE_MS (10 minutes by default; Opus 5.5 runs get at least 20).","operationId":"startRun","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"inputs":{"type":"object","description":"The run inputs. None is required here, but a useful report needs `input_0` and at least one file (`file_keys` or `Other_files`). Fields not listed are ignored.","properties":{"input_0":{"type":"string","description":"The primary input: the patient's intake (age, sex, goals, symptoms, medications, history) and any question or lab text. The IP guard screens it first; a blocked input ends the run with a refusal message in both reports.","example":"Synthetic patient, 46-year-old man. Goals: heart health, energy and sleep. No medications. Father had a heart attack at 52."},"Other_files":{"type":"array","items":{"type":"object","required":["metadata","raw_bytes","type"],"properties":{"metadata":{"type":"object","required":["name","mime_type"],"properties":{"name":{"type":"string","example":"labs.pdf"},"mime_type":{"type":"string","example":"application/pdf"}}},"raw_bytes":{"type":"string","format":"byte","description":"The file bytes, base64, without a `data:` prefix."},"type":{"type":"string","example":"file"}}},"description":"Files sent inline. PDFs are read for their text (scanned PDFs through Claude vision OCR); text and CSV files are read as UTF-8. Other types are read as UTF-8 too, so send lab images only to a server with the platform fixes on."},"Previous_Data":{"type":"string","default":"","description":"Earlier results or a previous report, for the longitudinal comparison (the `progress` template)."},"Report_Type":{"type":"string","default":"baseline","description":"The report template, given to the writers as text. The website sends `baseline` (Comprehensive), `focused` (the top 3 to 5 findings), `progress` (an interval comparison) or `snapshot` (a quick scorecard).","example":"baseline"},"language":{"type":"string","default":"English","description":"Every word of both reports is written in this language.","example":"English"},"overall_instructions":{"type":"string","default":"","description":"The clinic's own instructions, placed first in the clinical and visual report prompts: tone, emphasis, format."},"User_Preference_Level":{"type":"string","default":"Default","description":"Detail depth: how technical the explanations are, never how aggressive the interventions are. The website sends a directive that starts `DETAIL DEPTH: DEFAULT` or `DETAIL DEPTH: DETAILED`."},"intervention_philosophy":{"type":"string","enum":["conservative","balanced","progressive","aggressive"],"default":"balanced","description":"How far the recommendations go: lifestyle only, plus well-established supplements and standard medications, plus peptides and hormones, or full advanced protocols. Unknown values run as `balanced`."},"design":{"type":"string","enum":["classic","modern"],"default":"classic","description":"The visual report's design kit: `classic` (Cormorant Garamond, warm taupe) or `modern` (Zen Old Mincho and Familjen Grotesk, slate). Unknown values run as `classic`."},"protocols_active":{"type":"string","enum":["true","false"],"default":"false","description":"`\"true\"` turns on stage 9, protocol reconciliation. Anything else skips it and the draft report stands."},"protocols_store":{"type":"string","default":"","description":"The physician's protocol library, a Gemini File Search store, searched with a query built from the draft report.","example":"fileSearchStores/your-protocols-store"},"protocols_thisrun":{"type":"string","default":"","description":"A one-time physician directive for this run, applied verbatim with the highest priority."},"writer_model":{"type":"string","enum":["claude-opus-4-8","claude-opus-5-5","claude-sonnet-5-5"],"default":"claude-opus-4-8","description":"The Claude model that writes the report: stage 8 (clinical report), stage 9 (protocol reconciliation) and stage 11 (visual report). Other values are refused with 400."},"file_keys":{"type":"array","items":{"type":"string"},"description":"Keys returned by POST /api/files/upload or /api/files/presign-upload. Each file is read from object storage and added to `Other_files`."},"primary_input":{"type":"string","description":"Used as `input_0` when `input_0` is empty."}}},"file_keys":{"type":"array","items":{"type":"string"},"description":"Accepted here too, when `inputs.file_keys` is absent."},"variant":{"type":"string","description":"Accepted and ignored. The website sends `full` or `lean`."}}},"examples":{"bucket":{"summary":"Files uploaded first, passed as file_keys","value":{"inputs":{"input_0":"Synthetic patient, 46-year-old man. Goals: heart health, energy and sleep. No medications. Father had a heart attack at 52.","file_keys":["uploads/6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10.pdf"],"Previous_Data":"","Report_Type":"baseline","language":"English","overall_instructions":"","User_Preference_Level":"Default","intervention_philosophy":"balanced","design":"classic","protocols_active":"false","writer_model":"claude-opus-4-8"}}},"inline":{"summary":"A small text file inline","value":{"inputs":{"input_0":"Synthetic patient, 46-year-old man. Goals: heart health, energy and sleep. No medications. Father had a heart attack at 52.","Other_files":[{"metadata":{"name":"labs.txt","mime_type":"text/plain"},"raw_bytes":"TERMLUM6IDE0MiBtZy9kTCAocmVmIDwxMDApCkhiQTFjOiA1LjYgJQo=","type":"file"}],"Report_Type":"snapshot","intervention_philosophy":"conservative","writer_model":"claude-sonnet-5-5"}}}}}}},"responses":{"200":{"description":"The run started.","content":{"application/json":{"schema":{"type":"object","properties":{"taskId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["started"]},"pipeline_id":{"type":"string","example":"biomarker-analysis-v2"},"totalStages":{"type":"integer","example":12}},"example":{"taskId":"2b1f6c3e-8a4d-4c1b-9e0f-7d5a3c2b1e90","status":"started","pipeline_id":"biomarker-analysis-v2","totalStages":12}}}}},"400":{"description":"`writer_model` is not one of the allowed models, `inputs` is not an object, or the JSON is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"413":{"description":"The body is over the server's 100 KB JSON limit. Send large files through the bucket as `file_keys`.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"`file_keys` were sent but object storage is not set up.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Start a report","tags":["pipeline"]}},"/api/pipeline/setup":{"get":{"description":"Which services this server is set up with, the writer models a run may choose, and the run deadline. Booleans and names only; never a key or a URL.","operationId":"getSetup","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","description":"Booleans and names only; never a key or a URL.","properties":{"service":{"type":"string"},"build":{"type":"string","nullable":true,"description":"The deployed commit, when the host reports one."},"anthropic":{"type":"boolean","description":"ANTHROPIC_API_KEY is set (stages 1, 8, 9, 11)."},"gemini":{"type":"boolean","description":"GEMINI_API_KEY is set (stages 4 to 7 and 10)."},"knowledgeBaseGateway":{"type":"boolean","description":"A key for the ngm-geminiFS gateway is set: the knowledge base fallback and the protocol store search."},"storage":{"type":"boolean","description":"Object storage is set (file uploads and `file_keys`)."},"telemetry":{"type":"boolean","description":"DATABASE_URL is set, so every run writes its usage rows."},"platformFixes":{"type":"boolean","description":"PLATFORM_FIXES=on: lab table, patient context and checks."},"references":{"type":"boolean","description":"Stage 10, the Europe PMC references, is on."},"deadlineMs":{"type":"integer","description":"A run is stopped after this long (BIOMARKER_DEADLINE_MS)."},"writerDeadlinesMs":{"type":"object","additionalProperties":{"type":"integer"},"description":"The writer models whose runs get a longer deadline than `deadlineMs`, with that deadline."},"writerModels":{"type":"array","items":{"type":"string"}},"defaultWriterModel":{"type":"string"}},"example":{"service":"biomarker-analysis-v7","build":"4f2c9a1","anthropic":true,"gemini":true,"knowledgeBaseGateway":true,"storage":true,"telemetry":true,"platformFixes":false,"references":true,"deadlineMs":600000,"writerDeadlinesMs":{"claude-opus-5-5":1200000},"writerModels":["claude-opus-4-8","claude-opus-5-5","claude-sonnet-5-5"],"defaultWriterModel":"claude-opus-4-8"}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Read the server setup","tags":["pipeline"]}},"/api/pipeline/status/{jobId}":{"get":{"description":"The stage the run is in and, once `status` is `completed`, its `result`. Poll every 5 to 15 seconds. Runs live in this server's memory (the last 200): an id it does not know reads as `running` at stage 0, `Initializing`, as the website's poller expects.","operationId":"getRunStatus","parameters":[{"name":"jobId","required":true,"in":"path","description":"The `taskId` from POST /api/pipeline/start.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["running","completed","failed"]},"completed":{"type":"boolean"},"stage":{"type":"string","enum":["Initializing","IP Guard","Philosophy Router","File OCR","Query Expansion","Broad KB Retrieval","Gap Analysis","Targeted KB Retrieval","Clinical Report","Protocol Reconciliation","Reference Retrieval","Visual Report","Output Routing"]},"stageIndex":{"type":"integer","description":"1 to 12 while the run is known; 0 (with `Initializing`) for an id this server does not know."},"totalStages":{"type":"integer","example":12},"result":{"type":"object","properties":{"Visual_Report":{"type":"string","description":"The visual report, a complete HTML page. When the IP guard blocks the input: the refusal message."},"output_0":{"type":"string","description":"The clinical report in Markdown, ending with its REFERENCES section (papers looked up in Europe PMC). When blocked: the refusal message."},"gap_analysis":{"type":"string","description":"Stage 6: the knowledge base coverage gaps and the targeted follow-up query."},"processed_texts":{"type":"string","description":"The text read from each file, as `<document name=\"...\">` blocks."},"credits":{"type":"object","properties":{"report":{"type":"string"},"visual":{"type":"string"}},"description":"Credits of the clinical and visual reports, as text. The token counts of every call go to the telemetry table instead."},"db_write_status":{"type":"string","description":"The telemetry write, as JSON text: `success`, `error`, or `skipped` when DATABASE_URL is not set."},"checks":{"type":"object","description":"Only with PLATFORM_FIXES=on: what the checks found in the first visual report, and whether its one rewrite replaced it.","properties":{"version":{"type":"string"},"codes":{"type":"array","items":{"type":"string","enum":["value_mismatch","target_unit_mismatch","timing_conflict","context_conflict"]}},"rewritten":{"type":"boolean"}}},"truncated":{"type":"array","items":{"type":"string","enum":["output_0","Visual_Report"]},"description":"Only when it happened: the outputs the model stopped at its output limit, so their end is missing. A protocol rewrite that stops there is dropped for the complete draft instead."}},"description":"Present once `status` is `completed`."},"error":{"type":"string","description":"Present when `status` is `failed`."}},"example":{"status":"running","completed":false,"stage":"Broad KB Retrieval","stageIndex":5,"totalStages":12}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Poll a run","tags":["pipeline"]}},"/api/files/presign-upload":{"post":{"description":"A presigned PUT URL for one file, good for 15 minutes. PUT the bytes to `uploadUrl` with the same Content-Type and no Authorization header, then pass `key` in `inputs.file_keys`.","operationId":"presignUpload","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignUploadDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"uploadUrl":"https://<bucket host>/uploads/6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10.pdf?X-Amz-Signature=...","key":"uploads/6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10.pdf","fileId":"6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10","expiresIn":900}}}}},"400":{"description":"`name` is missing, or `mimeType` is not an allowed type.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"413":{"description":"`size` is over 200 MB.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"Object storage is not set up on this server.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Get an upload link","tags":["files"]}},"/api/files/upload":{"post":{"description":"Multipart form, field `files`: up to 10 files of at most 200 MB each, in PDF, JPEG, PNG, GIF, WebP, TXT, CSV or XLSX. Pass each `key` in `inputs.file_keys`.","operationId":"uploadFiles","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"1 file(s) uploaded successfully","files":[{"fileId":"6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10","name":"labs.pdf","mimeType":"application/pdf","size":78042,"key":"uploads/6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10.pdf"}]}}}}},"400":{"description":"No files, more than 10, or a type that is not allowed.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"413":{"description":"A file is over 200 MB.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"500":{"description":"Object storage refused or failed the call (for example, wrong bucket credentials). `message` is its answer.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"Object storage is not set up on this server.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Upload files","tags":["files"]}},"/api/files/list":{"get":{"operationId":"listFiles","parameters":[{"name":"prefix","required":false,"in":"query","description":"Default `uploads/`.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"prefix":"uploads/","files":[{"key":"uploads/6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10.pdf","size":84213,"lastModified":"2026-10-27T15:42:10.000Z"}],"total":1}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"500":{"description":"Object storage refused or failed the call (for example, wrong bucket credentials). `message` is its answer.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"Object storage is not set up on this server.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"List stored files","tags":["files"]}},"/api/files/signed-url/{path}":{"get":{"description":"A presigned GET URL for the key that follows `signed-url/`. `?expires=` in seconds, default 3600.","operationId":"getDownloadLink","parameters":[{"name":"expires","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"Object storage is not set up on this server.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Get a download link","tags":["files"]}},"/api/files/download/{path}":{"get":{"description":"The bytes of the key that follows `download/`.","operationId":"downloadFile","parameters":[],"responses":{"200":{"description":"The bytes of the file, with the Content-Type it was stored with.","content":{"application/json":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"404":{"description":"No file has that key, or storage could not read it.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"Object storage is not set up on this server.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Download a file","tags":["files"]}},"/api/files/{path}":{"delete":{"description":"Deletes the key that follows `/api/files/`.","operationId":"deleteFile","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"File deleted","key":"uploads/6f1c2a9e-1b7d-4e0a-9f51-0c2d8e7b3a10.pdf"}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"500":{"description":"Object storage refused or failed the call (for example, wrong bucket credentials). `message` is its answer.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"Object storage is not set up on this server.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"Delete a file","tags":["files"]}},"/api/reports/{jobId}":{"get":{"description":"The objects under `reports/{jobId}/`, each with a one-hour download link. This service returns its reports in the status response and stores none itself, so this answers 404 unless another service stored files for the id.","operationId":"listReportFiles","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The stored report files of the run.","content":{"application/json":{"schema":{"type":"object","example":{"jobId":"2ca30f04-9ed9-4501-928a-c863afe632c3","reports":[{"key":"reports/2ca30f04-9ed9-4501-928a-c863afe632c3/Visual_Report.html","type":"Visual_Report","size":52623,"lastModified":"2026-10-27T15:42:10.000Z","signedUrl":"https://<bucket host>/reports/…?X-Amz-Signature=..."}]}}}}},"401":{"description":"No `Authorization: Bearer` header.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"403":{"description":"The key is wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"404":{"description":"No report files are stored for the id.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"500":{"description":"Object storage refused or failed the call (for example, wrong bucket credentials). `message` is its answer.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}},"503":{"description":"Object storage is not set up on this server.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer"},"error":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"timestamp":{"type":"string","format":"date-time"},"path":{"type":"string"}},"example":{"statusCode":400,"error":"Bad Request","message":"writer_model must be one of: claude-opus-4-8, claude-opus-5-5, claude-sonnet-5-5","timestamp":"2026-10-23T09:30:00.000Z","path":"/api/pipeline/start"}}}}}},"security":[{"bearer":[]}],"summary":"List a run's stored report files","tags":["reports"]}}},"info":{"title":"Aevum Biomarker Analysis API","description":"Lab files and a patient intake in, an Aevum biomarker report out: a 12-stage pipeline that screens the input, reads the files, searches the NGM knowledge base, and has Claude write a clinical report (Markdown) and a visual report (HTML). Guide: /docs.","version":"1.0","contact":{}},"tags":[{"name":"pipeline","description":"Start a report and poll it. `Authorization: Bearer <API_KEY>`."},{"name":"files","description":"Upload lab files to object storage, for `file_keys`. Bearer."},{"name":"reports","description":"Report files stored for a run. Bearer."},{"name":"biomarker (v1)","description":"The direct contract: files inline as base64. `x-api-key: <KB_API_KEY>`."}],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"},"x-api-key":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"PresignUploadDto":{"type":"object","properties":{"name":{"type":"string","example":"labs.pdf"},"mimeType":{"type":"string","enum":["application/pdf","image/jpeg","image/png","image/gif","image/webp","text/plain","text/csv","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]},"size":{"type":"number","description":"Bytes; rejected with 413 if over 200MB"}},"required":["name","mimeType"]}}}}