Examples in these docs use synthetic data, not real people. Protearc reports findings, not a diagnosis.
ProtearcDocs

Get started

Quickstart

This walks the whole loop with the sandbox token: order a panel, hear when the result is released, read the released report, and see what changed against the member's own baseline. Everything is synthetic. The examples assume the local demo on http://localhost:8000; on another host, change the base URL.

1. Place an order

Send your own reference for the person (any string up to 128 characters; a name or birth date is never accepted), their sex, age band and the panel. The response gives the member's Protearc id and the requisition code for the sample.

curl -X POST http://localhost:8000/v1/orders \
  -H "Authorization: Bearer ptk_sandbox_meridian_demo" \
  -H "Content-Type: application/json" \
  -d '{"platform_ref": "meridian-user-1", "sex": "F", "age_band": "50-59", "panel": "brain"}'
json Response 201 (synthetic example)
{
  "member_id": "PA-482913",
  "requisition_code": "RQ-3F9A1C20"
}

2. Register a webhook

Tell Protearc where to send events. The response carries the signing secret, whsec_..., and it is shown only in this response, so keep it. The sandbox accepts http://localhost addresses; in every other case the address must be a public https:// URL. Registering the same address twice is refused with 409.

curl -X POST http://localhost:8000/v1/webhooks \
  -H "Authorization: Bearer ptk_sandbox_meridian_demo" \
  -H "Content-Type: application/json" \
  -d '{"url": "http://localhost:9000/hooks"}'
json Response 201 (synthetic example)
{
  "id": "5d0c8a1e-6b1f-4f6e-9b0e-0c9a4d7f2b11",
  "url": "http://localhost:9000/hooks",
  "created_at": "2026-11-04T16:20:00.000000Z",
  "secret": "whsec_example_not_a_real_secret"
}

3. Receive the event

When the laboratory releases a result, Protearc sends a signed POST to your address. This receiver checks the signature before it trusts the body, and answers 200 to acknowledge. Start it with the secret in PROTEARC_WEBHOOK_SECRET. The Webhooks page has the details of the headers, the signature and the retries.

Receiver, Python (standard library)
import hashlib
import hmac
import json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer

SECRET = os.environ["PROTEARC_WEBHOOK_SECRET"]


def verify(secret, timestamp, body, signature):
    signed = timestamp.encode() + b"." + body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected.encode(), signature.encode())


class Hook(BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
        if not verify(SECRET, self.headers.get("X-Protearc-Timestamp", ""), body,
                      self.headers.get("X-Protearc-Signature", "")):
            self.send_response(401)
            self.end_headers()
            return
        event = json.loads(body)
        print(event["type"], event["data"]["member_ref"])
        self.send_response(200)
        self.end_headers()


if __name__ == "__main__":
    HTTPServer(("", 9000), Hook).serve_forever()
Receiver, Node (built-in modules)
const crypto = require("node:crypto");
const http = require("node:http");

const SECRET = process.env.PROTEARC_WEBHOOK_SECRET;

function verify(secret, timestamp, body, signature) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret)
    .update(`${timestamp}.`).update(body).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

http.createServer((req, res) => {
  const chunks = [];
  req.on("data", (c) => chunks.push(c));
  req.on("end", () => {
    const body = Buffer.concat(chunks);
    if (!verify(SECRET, req.headers["x-protearc-timestamp"] ?? "", body, req.headers["x-protearc-signature"] ?? "")) {
      res.writeHead(401).end();
      return;
    }
    const event = JSON.parse(body);
    console.log(event.type, event.data.member_ref);
    res.writeHead(200).end();
  });
}).listen(9000);
json The event body (synthetic example)
{
  "created_at": "2026-11-04T16:31:08.512345+00:00",
  "data": {
    "draw_number": 4,
    "member_ref": "PA-0001",
    "report_id": "3b6f0c9e-52c4-4a43-8f27-7d1f0a6e9c10",
    "status": "released"
  },
  "id": "9c1d7a52-0e0b-4a4e-a4f5-2f6f9d1b7c33",
  "type": "result.released"
}

4. Fetch the report

Use data.member_ref from the event. The report has one entry per released draw; values are NPQ (normalized protein quantification, log2 scale). Add ?format=fhir for a FHIR R4 Bundle of DiagnosticReport and Observation resources, with media type application/fhir+json. Only released results are ever returned. A member that is not yours, or does not exist, is 404.

curl http://localhost:8000/v1/members/PA-0001/reports \
  -H "Authorization: Bearer ptk_sandbox_meridian_demo"

curl "http://localhost:8000/v1/members/PA-0001/reports?format=fhir" \
  -H "Authorization: Bearer ptk_sandbox_meridian_demo"
json Response 200 (JSON, synthetic example, shortened)
{
  "member_id": "PA-0001",
  "reports": [
    {
      "report_id": "3b6f0c9e-52c4-4a43-8f27-7d1f0a6e9c10",
      "draw_number": 4,
      "drawn_on": "2026-10-01",
      "released_at": "2026-11-04T16:31:08.512345Z",
      "released_by": "Dr. A. Okafor, Laboratory Director",
      "performing_laboratory": { "name": "Example Laboratory", "clia_number": "00D0000000" },
      "results": [
        { "protein": "plasma_ptau217", "name": "p-tau217", "unit": "NPQ", "value": 4.1 }
      ]
    }
  ]
}

5. Read what changed

Changes compare each protein's latest released value with the member's own baseline, which is the mean of their first three earlier released draws. Until there is a baseline the status is baseline_building. The response reports findings and which model version read them; it is not a diagnosis.

curl http://localhost:8000/v1/members/PA-0001/changes \
  -H "Authorization: Bearer ptk_sandbox_meridian_demo"
json Response 200 (synthetic example, shortened)
{
  "member_id": "PA-0001",
  "model": { "name": "example-model", "version": "1.0.0" },
  "groups": [
    {
      "panel_id": "brain",
      "group": "tau",
      "name": "Tau",
      "status": "rose",
      "proteins": [
        { "protein": "plasma_ptau217", "name": "p-tau217", "unit": "NPQ",
          "status": "rose", "baseline": 3.2, "latest": 4.1 }
      ]
    }
  ]
}

Next