Nosis Biomarker Analysis API — Integration Guide

For the Nosis engineering team. Covers auth, the upload → run → poll flow, the request and response shapes, limits, versioning, and data handling.

  • Template: nosis-blueprint v0.5.14 · reference ranges v0.1.0
  • Contact: Jeph (NGM engineering)
  • Last updated: 2026-09-10
Environment
Sandbox · Nosis HealthSynthetic or de-identified data only.
Tier
DetailedFull retrieval depth.
Template
nosis-blueprint v0.5.31Stamped on every report as template_version.
Reference ranges
v0.1.0Stamped on every report as ranges_version.
Interactive reference
OpenAPI, with live schemasTry each endpoint with your key.
Service status
/healthVersions and environment, no key needed.

Release 0.5.14 — 10 September 2026#

Report acceptance and correction#

Task status completed means processing has finished. Check result.validation.passed and the returned artifacts separately. When narrative or source checks fail after the single correction attempt, Markdown and audit metadata are retained and HTML is withheld. Keep that result flagged in the Review Queue; it is not an approved report.

The correction receives the complete reviewed draft and supported findings as untrusted data, so it can preserve valid content while correcting the identified claims. Reporting bounds are kept distinct from assay limits, and missing source units must remain missing in narrative text. Nosis Gap Analysis may retry a recognized transient provider failure once within its original deadline; this does not create a new task or increase the report-correction budget.

Platelet count units such as Thousand/uL are supported. Assessment safety copy reflects the Assessment pathway, and generated HTML excludes internal stylesheet comments. The interactive reference at /api and its OpenAPI document at /api-json now describe nested request fields and success/error responses. Existing URLs, credentials, request fields and historical reports are unchanged.

This update also improves Dutch biomarker matching and supported SI-unit conversions, clarifies assay and collection-date guidance, and adds source range units and selected collection dates to panel audit metadata. Blank result cells are excluded before selecting the latest collected result. Wrapped assay names are kept together, and the equivalent decimal-comma eGFR unit is supported. Narrative generation uses the available source unit for unconverted results and requires supplied evidence for statements about habits or collection timing. New reports use ISO YYYY-MM-DD collection dates; ambiguous or invalid dates are omitted, and older reports retain their original representation. Both tiers perform source-grounding review and retry a nonempty malformed review decision at most once while retaining the same report and source evidence. Valid negative decisions do not receive a retry to seek approval. Panel rows may include source_name, the selected laboratory assay label. It is source data, not an instruction. source: "measured" means reported by the laboratory and does not establish a direct measurement method; server-calculated rows omit source_name. Recognized Dutch current/previous-result tables can supply dates or missing reported rows when their source cells match unambiguously. Ambiguous layouts remain unchanged. Explicit narrative status claims must agree with the panel; a contradiction that persists after report repair withholds HTML and is surfaced in validation.

Generation and review now use one fact representation that separates reported results, laboratory and server calculation origins, known assay methods, and reference bounds. Explicit contradictory prose and unsupported low/high-normal status labels receive one report correction attempt. If they persist, or source review fails or is unavailable, validation.passed is false and outputs.html is false. The flagged Markdown and audit metadata remain available for your Review Queue. Use these fields when deciding whether a report may advance; a completed task means processing ended, not that the report passed validation. Reference retrieval runs against the final reviewed draft.

1. Base URLs and credentials#

Environment Base URL Key
Sandbox (detailed) https://nosis-sandbox.api.nextgenerationmedicine.co sandbox key
Sandbox (lean) https://nosis-biomarker-analysis-lean-sandbox-production.up.railway.app its own sandbox key
Production (detailed) https://nosis.api.nextgenerationmedicine.co — not yet live production key
Production (lean) https://nosis-lean.api.nextgenerationmedicine.co — not yet live production key

This guide is published at /docs on your host, and https://sandbox.api.nextgenerationmedicine.co/nosis (or /nosis/lean) is the short link that redirects there. The interactive OpenAPI reference is at /api.

Your deployment is yours alone: its own host, key, template and reference ranges. The two sandbox tiers are separate services with separate keys; the report contract is identical, lean runs fewer retrieval stages (totalStages 10 rather than 11) and reports pipeline_id: nosis-blueprint-lean. https://nosis-biomarker-analysis-sandbox-production.up.railway.app is the same service under its platform hostname and also works. Production hosts are listed for planning only — they do not exist yet and no production key has been issued.

If you received an earlier guide (25 August) describing /api/developer/v1/biomarker/... routes, discard it. That was a different gateway. This document is the contract: /api/files, /api/pipeline, /api/reports on the host above.

Sandbox and production use separate URLs and separate keys. A sandbox key is rejected by production and vice versa.

Sandbox is synthetic-data only. Do not send real member data until the BAA is executed. Production keys are not issued before then.

The lean host runs the same report contract on a lighter retrieval pipeline (3 knowledge-base reads instead of 6). Output shape is identical.

Auth — Bearer token on every request:

http
Authorization: Bearer <your-api-key>

Missing or wrong keys return 401 / 403 as JSON on authenticated data endpoints. Documentation, Swagger and health endpoints are public.

The integration key needs files:write, files:read, pipeline:run, pipeline:read and reports:read; deletion also needs files:delete. Keys are bound to the client, environment and instance and can expire or be revoked. The existing sandbox developer keys expire on 9 October 2026 UTC; arrange renewal before then. Revocation blocks subsequent authenticated API calls, but already issued signed storage URLs remain valid until their expiry.


2. The flow#

  1. POST /api/files/presign-upload   →  presigned PUT URL + key
  2. PUT  <presigned url>             →  upload the lab PDF directly
  3. POST /api/pipeline/start         →  { taskId }
  4. GET  /api/pipeline/status/{id}   →  poll until completed
  5. GET  /api/reports/{id}           →  signed URLs for the stored artifacts

Endpoints#

Endpoint Purpose
POST /api/files/presign-upload Presigned PUT URL for one lab document, valid 15 minutes.
POST /api/files/upload Multipart upload, field files, up to 10 per request.
GET /api/files/list?prefix=uploads/ List your uploaded objects under a prefix.
GET /api/files/signed-url/{key} Presigned download URL; ?expires= must be an integer from 1 to 3600 seconds, default 3600.
GET /api/files/download/{key} Stream an object's bytes.
DELETE /api/files/{key} Delete an object immediately.
POST /api/pipeline/start Start a run. Returns taskId at once.
GET /api/pipeline/status/{taskId} Poll a run; carries the report inline while it is cached.
GET /api/reports/{taskId} Signed URLs for the stored artifacts. The durable copy.
GET /health Service status and the versions in use. No key required.

The data endpoints above require the Bearer key. /docs, Swagger at /api and /health are public.

Step 5 is required, not optional. Step 4 usually returns the full report inline, but that copy is served from an in-memory cache of the 200 most recent jobs. After a service restart, on a second instance behind the load balancer, or once 200 further jobs have run, step 4 returns "completed": true with no result field. The bucket copy that step 5 points at is the durable one.

Treat inline result as a fast path and step 5 as the source of truth: if completed is true and result is absent, fetch the artifacts. Do not treat a missing result on a completed job as a failure.

2.1 Upload a lab document#

http
POST /api/files/presign-upload
Content-Type: application/json

{ "name": "member-labs.pdf", "mimeType": "application/pdf", "size": 482113 }
json
{ "uploadUrl": "https://...", "key": "uploads/9f3c....pdf", "fileId": "9f3c...", "expiresIn": 900 }

PUT the bytes to uploadUrl with the same Content-Type, then pass key in file_keys. The presigned URL is valid for 15 minutes.

Accepted types: PDF, JPEG, PNG, GIF, WebP, TXT, CSV, XLSX.

Alternative for small files: POST /api/files/upload as multipart/form-data with field files (up to 10 per request).

2.2 Start a run#

http
POST /api/pipeline/start
Content-Type: application/json

{
  "idempotency_key": "nosis-report-7f21c9",
  "inputs": {
    "member": {
      "name": "Jordan Reeves",
      "age": 42,
      "sex": "male",
      "program_status": "core",
      "substance_use_status": "In recovery, alcohol-free since March 2024",
      "height_cm": 178,
      "weight_kg": 82
    },
    "input_0": "MEMBER CONTEXT\n\nDemographics: ...\nGoals & Priorities: ...\nSleep: ...",
    "overall_instructions": "<your current Part II instruction package>",
    "file_keys": ["uploads/9f3c....pdf"],
    "language": "English",
    "User_Preference_Level": "Detailed"
  }
}
json
{
  "taskId": "0f8c2e1a-...",
  "status": "started",
  "pipeline_id": "nosis-blueprint-detailed",
  "environment": "sandbox",
  "totalStages": 11,
  "template_id": "nosis-blueprint",
  "template_version": "0.5.14",
  "ranges_version": "0.1.0"
}

Fields

Field Required Notes
member yes See below. Populates the report header directly.
input_0 yes Free-text member context (your Typeform summary). Max 60,000 chars.
file_keys or inputs.Other_files yes At least one lab document.
overall_instructions no Your Part II package. Max 120,000 chars.
Previous_Data no Prior results for longitudinal context. Without it the report treats the labs as a snapshot and will not describe anything as trending.
language no Default English. See §7.
User_Preference_Level no Detailed (default on the detailed host) or Default.
idempotency_key no See §4.

member:

Field Required Constraint
name yes 1–120 chars
age yes integer 18–120
sex yes male | female — selects sex-specific ranges and markers
program_status yes assessment | core — selects the §X pathway copy
substance_use_status no ≤300 chars, member-provided. Never inferred; omitted from the header when absent.
height_cm no 100–250 — required for BMI
weight_kg no 30–300 — required for BMI

For the agreed Nosis integration, include height, weight, age and sex with every request so all applicable calculations, including BMI, can be produced.

Date of birth is not accepted and is never rendered.

Ignored if sent (forced server-side): Report_Type, intervention_philosophy, design, protocols_store, protocols_thisrun, protocols_active, Protocol_files. Sending them is not an error.

2.3 Poll for completion#

http
GET /api/pipeline/status/{taskId}

Running:

json
{
  "status": "running",
  "completed": false,
  "stage": "Clinical Report",
  "stageIndex": 9,
  "totalStages": 11,
  "created_at": "2026-09-04T09:12:03.000Z",
  "updated_at": "2026-09-04T09:18:44.000Z",
  "expires_at": "2026-09-11T09:12:03.000Z"
}

Completed (abridged):

json
{
  "status": "completed",
  "completed": true,
  "stage": "Output Routing",
  "stageIndex": 11,
  "totalStages": 11,
  "template_id": "nosis-blueprint",
  "template_version": "0.5.14",
  "ranges_version": "0.1.0",
  "validation": { "passed": true, "errors": [], "warnings": [] },
  "result": {
    "output_0": "# Your Nosis Blueprint Report\n\nGenerated September 4, 2026\n\n...",
    "Visual_Report": "<!doctype html>…",
    "outputs": { "markdown": true, "html": true },
    "panel": {
      "ranges_version": "0.1.0",
      "categories": [
        {
          "key": "metabolic",
          "name": "Blood Sugar & Metabolic Health",
          "rows": [
            {
              "key": "hba1c",
              "display_name": "Hemoglobin A1c",
              "value": 5.2,
              "unit": "%",
              "reference_range": { "lo": 4, "hi": 5.4 },
              "status": "Optimal",
              "source": "measured",
              "lab_range": { "lo": 4, "hi": 5.6 },
              "lab_range_unit": "%",
              "collected_at": "2026-09-01"
            }
          ]
        }
      ]
    },
    "unmapped_biomarkers": [],
    "regenerations": 0,
    "credits": { "report": "12", "visual": "0" }
  }
}

result.output_0 is the member-facing report in Markdown. That is the deliverable; everything else is metadata for your review queue.

result.Visual_Report is the same report as a standalone HTML document, and outputs.html is true. It is rendered in code from the validated Markdown — not generated by a model — so it is byte-deterministic, carries exactly the words of output_0 and nothing else, and adds no time or cost to the run. It is a complete page: inline stylesheet, no external fonts, scripts or network calls, print-ready (Letter, @page margins, tables never split mid-row) so you can show it directly, drop it into your review queue, or hand it to a PDF renderer as-is.

Structure of the HTML, should you want to hook into it:

If you would rather not parse Markdown or HTML for the numbers, don't: result.panel carries every biomarker as structured JSON — category, display name, value, unit, reference range, status and whether it was measured or calculated. The Section I tables in both formats are rendered from exactly that object, so the three can never disagree.

Failed:

json
{
  "status": "failed",
  "completed": false,
  "error": "lab_extraction_failed",
  "error_category": "lab_extraction_failed"
}

Unknown or expired id:

json
{ "statusCode": 404, "error": "job_not_found", "message": "No job with that id..." }

2.4 Retrieve stored artifacts#

http
GET /api/reports/{taskId}

Returns output_0.md, Visual_Report.html and meta.json with signed URLs valid for 1 hour. meta.json holds the panel, validation, versions and timings — no member name and no free-text input.


3. Polling guidance#

Do not use POST /v1/biomarker/run — the synchronous endpoint is not exposed here, and would exceed the HTTP timeout anyway.


4. Limits#

Limit Value Response when exceeded
Requests per minute, all /api/* 120 429
POST /api/pipeline/start per minute 10 429
Concurrent running jobs 5 429 with {"error":"concurrency_limit","limit":5,"retry_after_seconds":60} and a Retry-After header
overall_instructions 120,000 chars 413 with {"error":"instructions_too_long","limit":120000}
input_0 60,000 chars 400
Upload size see /api 413

Idempotency. Send idempotency_key on start. A repeat with the same key within 24 hours returns the original taskId with status: "existing" instead of starting a second run. Recommended on every submit — it makes your retries safe.

The admission decision is atomic within the deployment's client, environment, pipeline and configured instance. After 24 hours the key may start a new job; the earlier job retains its separate seven-day audit record. Use a fresh key when intentionally requesting a new report with changed inputs.


5. How your instructions are used#

overall_instructions is your Part II package. It is passed to the report generator verbatim and shapes tone, emphasis, priorities and what each section sets out to achieve. Omitting it uses the account's startup-configured guidance. A supplied string replaces that default; an explicit empty string clears the default guidance for that request. The Nosis shell and safety rules still apply.

Precedence — highest first:

  1. Safety and scope rules (NGM) — no diagnosis, no prescription decisions, no detox or taper instructions, no peptides/hormones/IV protocols, no outcome-timeline promises.
  2. The fixed shell (NGM) — the 13 sections, their order and headings, the biomarker categories, the table structure, and the approved §X and §XII copy.
  3. Your guidance (Nosis) — everything else.

Ordinary content or presentation preferences cannot override 1 or 2. Attempts to bypass security or clinical safety rules are rejected by screening, as described below. Authorized guidance can change per request without a deploy.

Constraints:

Your instructions are screened in the context of authorized enterprise report guidance. References to the configured shell, fixed copy, tone, section guidance or clinical rationale are allowed. Attempts to extract hidden instructions, disclose private data or bypass server safety rules are rejected before lab extraction and report generation with error_category: "instructions_rejected". Member context is screened separately; rejected or malformed screening decisions fail with error_category: "input_rejected". Screening itself uses a model.


6. Report structure#

output_0 always contains a generated header followed by exactly these level-2 headings, in this order:

# Your Nosis Blueprint Report      (header: Generated / Member / Age / Substance Use Status)

## Your Snapshot
## I. Your Complete Biomarker Panel
## II. Your Results in Context
## III. Your Action Plan
## IV. Your Food and Nutrition Plan
## V. Your Supplement Guide
## VI. Your Exercise and Movement Plan
## VII. Your Sleep Plan
## VIII. Your Nervous System Regulation Plan
## IX. Your Real Strengths
## X. What Happens Next
## XI. Sources and Further Reading
## XII. Important Information About This Report

Section I#

One ### <category> per category that has collected biomarkers, each followed by a results table then #### What This Means. Categories with no collected biomarkers are absent entirely — no empty headings, no blank rows.

Category order is fixed: Blood Sugar & Metabolic Health · Cholesterol & Cardiovascular Health · Liver Health · Kidney Function & Electrolytes · Blood & Immune Health (Red Blood Cells / White Blood Cells & Immune Function / Platelets) · Thyroid Health · Hormones & Reproductive Health · Nutrients & Iron Status · Fatty Acid Balance.

Tables are generated by us, not by the model:

| What We Measured | Your Result | Reference Range | Status |

Status vocabulary#

Exactly one of:

Optimal · Suboptimal (low) · Suboptimal (high) · Out of range (low) · Out of range (high) · Not classified

Not classified means a reliable classification is unavailable. Reasons include missing applicable ranges, unconfirmed fasting, censored/non-numeric results or unsupported units. Where no applicable range is available, reference_range is null and the displayed range is —. Review the warnings; the narrative must not invent a good/bad classification.

Reference Range is NGM's target range, not the source laboratory's. The lab's own interval is returned separately as lab_range for your reconciliation. These bounds remain in the original laboratory units, identified by lab_range_unit, even when value and unit have been converted to the panel's display units. For example, calcium may have value: 10 and unit: "mg/dL" while lab_range remains { "lo": 2.15, "hi": 2.55 } with lab_range_unit: "mmol/L". The source interval does not determine Status. lab_range_unit is present only when lab_range is non-null, and is null if the source unit is unknown. Older stored reports may omit this field.

Measured rows also include collected_at when the selected source result has a collection date. When duplicate biomarkers have different dates, this is the selected latest date. Unknown dates and calculated rows omit the field; no collection date is inferred for a calculated metric. This audit metadata does not add columns to the report tables.

Ranges and statuses are deterministic for the same marker, normalized value and unit, applicable age/sex/fasting context, and range-table version. Table v0.1.0 was seeded from existing report-prompt thresholds and conventional adult ranges. It remains unsigned pending clinical approval and must not be treated as a clinically approved production catalogue.

Specific clinical review blocker: vitamin D's seeded target is 60–80 ng/mL. It can label a conventionally adequate 40 ng/mL Suboptimal (low). This target requires clinical correction/review and sign-off; it must not become a reason to raise a healthy member's level or recommend a supplement dose. Template 0.5.6 enforces that interpretation boundary and requires an independent, applicable member-specific indication for targeted supplement advice. Normal limited panels also cannot establish overall cardiovascular risk or rule out organ disease.

Calculated metrics#

Computed by us in code when the inputs are present, labelled Calculated (BMI is labelled Platform-derived):

BMI · HOMA-IR · AST/ALT Ratio · FIB-4 · Triglyceride/HDL · Neutrophil-to- Lymphocyte Ratio · Free Androgen Index (women) · Free Testosterone (Vermeulen) · Non-HDL · Total Cholesterol/HDL · Globulin · Albumin/Globulin.

A metric whose required inputs are missing or unusable is absent. Free Testosterone requires measured total testosterone, SHBG and albumin; albumin is never assumed. HOMA-IR requires confirmed fasting glucose and insulin. Calculations are omitted when their inputs have conflicting collection dates. Relevant omissions return calculation_unavailable warnings for your review queue. Unknown collection dates require source review.

Measured results take precedence over calculations, including a reported inequality such as <1. Such results retain their printed value and are not treated as exact numbers for calculation or status classification. Supported unit conversions are deterministic, including albumin g/L to g/dL and insulin mIU/L to µIU/mL.

CBC differentials show absolute counts only; percentage rows are dropped.


7. Validation#

Every report is checked structurally before return. Every output must enter the Nosis Review Queue for human review; do not publish automatically, including reports where validation.passed is true. Structural validation is not clinical approval.

json
"validation": { "passed": true, "errors": [], "warnings": ["unmapped_biomarkers: CA-125"] }

Warnings you will see:

Warning Meaning
unmapped_biomarkers: ... Reported biomarkers outside the Nosis panel. Excluded from tables; still available to the narrative.
unreadable_values: n Values present but not usable as exact numbers. Printed text is retained where available and the row is Not classified.
unit_mismatch: ... Reported in a unit we cannot convert. Not classified.
duplicate_biomarker: n Same biomarker twice; latest collection date kept.
calculation_unavailable: ... A required input is missing, unusable, lacks fasting confirmation, or has a conflicting collection date. No invented calculation is displayed.
fasting_not_confirmed: ... Glucose or insulin lacks explicit fasting provenance. It stays visible without a fasting-only reference range or classification.
redacted_term: "X" ×n A vendor or tooling term was removed from the text.
third_person_voice: n Possible drift out of second person.
fixed_copy_language_mismatch Non-English run; §X and §XII remain English (English-only in v1).
model_emitted_table The model wrote a table in §I; it was stripped and replaced with ours.

8. Versioning#

Start/status responses and saved report metadata identify the report contract:

Field Meaning
template_id nosis-blueprint
template_version Report shell, section intents and fixed copy
ranges_version Reference-range table

Record these versions with each report. Saved meta.json also includes job_id, client, environment, pipeline_id, inputs_hash, configuration_hash (when account configuration is active), generation time, durations, usage, panel and validation. The effective-input hash covers source contents, demographics and guidance; the configuration hash identifies the immutable account settings. Upload and error responses do not all carry this metadata. Treat these artifacts as health data.

Semver, and they move independently. Changes are logged in src/clients/nosis/CHANGELOG.md; we will notify you before any bump that changes report structure.


9. Data handling#


10. Error reference#

HTTP error Meaning
400 validation_failed Missing or invalid field. fields names them.
400 no_lab_file No file_keys and no inline Other_files.
400 invalid_overall_instructions Not a string.
401 / 403 — Missing or invalid Bearer token.
404 job_not_found Unknown or expired taskId.
413 instructions_too_long overall_instructions over 120,000 chars.
429 concurrency_limit More than 5 jobs running. Honour Retry-After.
429 — Request-rate limit.
503 — Object storage or a required credential is not configured.

Job-level failures (status: "failed") carry error_category:

input_rejected · instructions_rejected · lab_extraction_failed · deadline_exceeded · worker_restarted · artifact_persistence_failed · validation_failed · pipeline_error

input_rejected means member-context screening rejected the request or could not produce a valid decision. instructions_rejected applies to the separate Nosis guidance screening. Neither failure proceeds to lab extraction or report generation.

artifact_persistence_failed means the required report artifacts were not successfully saved; that job is failed even if generation finished.

worker_restarted is the age-based stale-job category. It can indicate a worker restart, stalled work or a terminal state that could not be saved; it is not proof of a restart. Work is not resumed from a checkpoint. After confirming this terminal failure, use a new idempotency key for a deliberate rerun. Reusing the original key within its 24-hour window returns the original failed job. Do not start a duplicate solely because polling timed out. Completed-report durability is separate: completed status, validation and artifact references survive a worker restart. Schedule deployments with no pending/running jobs where possible.


11. Reference and status#

When a reported value cannot be converted and its source omitted the unit, source_unit is an empty string. The displayed raw result has no inferred unit and remains Not classified; do not apply the canonical unit field to that raw value.