Support · API Reference

Lab System Integration API

Three integration patterns. POST /api/lis-data — LISBridge pushes parsed results to your HIS. POST /api/orders — your HIS pushes orders to LISBridge. Order Pull — LISBridge polls your HIS for orders (NAT-friendly, no open ports needed).

POST /api/lis-dataPOST /api/ordersOrder Pull (polling)No SDK required
On this page▾
Overview

How LISBridge sends data to your lab system

Every time the LISBridge desktop app parses a result from a connected instrument, it forwards that result to a server you control with a single HTTP POST. The destination is the Base URL you enter in LISBridge, with the fixed path /api/lis-data appended — so LISBridge always posts to {baseUrl}/api/lis-data. There is no polling and no SDK: your lab system just needs an endpoint at that path that accepts JSON.

This page documents exactly what LISBridge sends, what your endpoint should return, and includes a working receiver you can copy into your own project.

Note: The endpoint path is fixed. LISBridge appends /api/lis-data to your Base URL automatically — you choose the host, but the route must be /api/lis-data.
Prerequisites

What you need

✓LISBridge desktop app

Installed on the machine connected to your instruments via serial or TCP/IP, with at least one instrument sending data.

✓A reachable HTTP server

Your own lab system, exposing a POST /api/lis-data route that accepts application/json and is reachable from the LISBridge machine.

✓The server's Base URL

Entered in LISBridge Settings → Connection. LISBridge appends /api/lis-data to it.

How It Works

Three steps to integration

1

Add a /api/lis-data route to your lab system

Create a POST route at /api/lis-data that accepts application/json. See the receiver examples below.
2

Enter your Base URL in LISBridge

Open Settings → Connection and paste your server's Base URL (e.g. https://lab.example.com). Do not include /api/lis-data — LISBridge adds it.
3

Results flow automatically

From now on, every parsed result is POSTed to {your Base URL}/api/lis-data within milliseconds of arriving from the instrument.
Request Format

Request format

MethodPOST
URL{your Base URL}/api/lis-data
Content-Typeapplication/json
EncodingUTF-8
Payload Reference

JSON payload schema

LISBridge sends one POST per result. The body always includes raw_data, client_ip, and token. When the message parsed successfully it also includes a parsed_data object. Here is a complete example followed by field-by-field documentation.

json
{
  "raw_data": "H|\\^&|||Sysmex^XN-1000|||||||P|1\rO|1|S20260522-014||^^^CBC\r...",
  "client_ip": "192.168.1.50",
  "token": "987654321",
  "parsed_data": {
    "order_id": "S20260522-014",
    "machine_name": "Sysmex XN-1000",
    "findings": [
      { "parameter": "WBC", "value": "6.5",  "unit": "10^3/uL" },
      { "parameter": "HGB", "value": "14.2", "unit": "g/dL" },
      { "parameter": "PLT", "value": "250",  "unit": "10^3/uL" }
    ]
  }
}
Important: parsed_data is optional — it is omitted when LISBridge could not parse the message (you still receive raw_data). Within it, machine_name and each finding's unit can be null. Always null-check before saving.

Top-level fields

raw_datastringrequired
The original, unparsed instrument message exactly as received (ASTM, HL7, or custom).
client_ipstringrequired
IP address of the instrument or device that sent the data.
tokenstringrequired
Static value LISBridge includes in every request. Not used for authentication — you can ignore it.
parsed_dataobjectoptional
Present only when the message parsed successfully. Omitted on parse failure.

parsed_data fields

order_idstringrequired
Sample / order identifier from the instrument.
machine_namestring | nulloptional
Resolved instrument name (e.g. "Sysmex XN-1000"). null when LISBridge cannot identify it.
findingsarrayrequired
One object per test result (see below).
imagesarrayoptional
Present only when the instrument transmitted images (see below).

finding object

parameterstringrequired
Test parameter code or name (e.g. "WBC", "HGB").
valuestringrequired
Result value as a string (preserves trailing zeros and non-numeric values).
unitstring | nulloptional
Unit of measurement (e.g. "g/dL"). null when none was provided.

image object (only inside images)

parameterstringrequired
Parameter the image is associated with.
image_typestringrequired
Image format/type as reported by the instrument.
encodingstringrequired
Encoding of base64_data (e.g. "base64").
base64_datastringrequired
The encoded image bytes.
file_sizenumber | nulloptional
Size in bytes, when known.
widthnumber | nulloptional
Pixel width, when known.
heightnumber | nulloptional
Pixel height, when known.
Response Handling

Response handling

Return any 2xx status to acknowledge receipt. LISBridge treats 2xx as success; any other status, a timeout, or an unreachable server is shown as a forwarding error in the app. The raw data remains stored in LISBridge so it can be re-sent later with the manual forward action.

A typical success response body looks like this (the body is informational — LISBridge keys off the status code):

json
{ "status": "success", "saved": 3 }

2xx — Success

Return 200 once you have stored the result. LISBridge marks the delivery complete.

4xx — Client error

Return on a malformed or rejected request. The result stays in LISBridge for manual re-forwarding.

5xx — Server error

Return on a storage failure. LISBridge reports the error; the raw data is retained for manual re-send.

Timeout / unreachable

Shown as a forwarding error. Make sure your server is reachable from the LISBridge machine.

Tip: If you run the MediSpa package, LISBridge inspects your response body for an annotation string. Standard integrations can ignore this and simply return 200.
Code Examples

Receiver examples

Drop-in /api/lis-data receivers. Copy one, swap in your own storage, and you are done. Each handles the optional parsed_data and stores every finding.

Next.js (App Router)

typescript
// app/api/lis-data/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  const body = await request.json();

  // 1. parsed_data is only present when LISBridge parsed the message
  const parsed = body.parsed_data;
  if (!parsed) {
    // Parse failed upstream — keep raw_data for review, then acknowledge
    // await saveRaw(body.raw_data, body.client_ip);
    return NextResponse.json({ status: "received", saved: 0 }, { status: 200 });
  }

  // 2. Persist each finding
  const rows = parsed.findings.map((f: { parameter: string; value: string; unit: string | null }) => ({
    order_id: parsed.order_id,
    machine_name: parsed.machine_name, // may be null
    parameter: f.parameter,
    value: f.value,
    unit: f.unit,                      // may be null
    client_ip: body.client_ip,
  }));
  // await db.machineTestResult.createMany({ data: rows });

  return NextResponse.json(
    { status: "success", saved: rows.length },
    { status: 200 },
  );
}

Node.js / Express

javascript
const express = require('express');
const app = express();
app.use(express.json({ limit: '10mb' })); // images can be large

app.post('/api/lis-data', (req, res) => {
  const { client_ip, parsed_data } = req.body;

  // 1. parsed_data is absent on parse failure
  if (!parsed_data) {
    return res.status(200).json({ status: 'received', saved: 0 });
  }

  // 2. Store findings
  const { order_id, machine_name, findings } = parsed_data;
  for (const f of findings) {
    // INSERT INTO results (order_id, machine_name, parameter, value, unit, client_ip)
    console.log(`${order_id} ${machine_name ?? '?'} ${f.parameter}=${f.value} ${f.unit ?? ''}`);
  }

  res.status(200).json({ status: 'success', saved: findings.length });
});

app.listen(3001, () => console.log('Listening on :3001  POST /api/lis-data'));

PHP

php
<?php
header('Content-Type: application/json');

$body = json_decode(file_get_contents('php://input'), true);

// 1. parsed_data may be absent on parse failure
$parsed = $body['parsed_data'] ?? null;
if (!$parsed) {
    http_response_code(200);
    echo json_encode(['status' => 'received', 'saved' => 0]);
    exit;
}

// 2. Store findings
$orderId = $parsed['order_id'];
$machine = $parsed['machine_name'] ?? null;   // may be null
foreach ($parsed['findings'] as $f) {
    $parameter = $f['parameter'];
    $value     = $f['value'];
    $unit      = $f['unit'] ?? null;          // may be null
    // $stmt->execute([$orderId, $machine, $parameter, $value, $unit, $body['client_ip']]);
}

http_response_code(200);
echo json_encode(['status' => 'success', 'saved' => count($parsed['findings'])]);

Python / Flask

python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/lis-data', methods=['POST'])
def lis_data():
    body = request.get_json()

    # 1. parsed_data is absent on parse failure
    parsed = body.get('parsed_data')
    if not parsed:
        return jsonify({'status': 'received', 'saved': 0}), 200

    # 2. Store findings
    order_id = parsed['order_id']
    machine  = parsed.get('machine_name')      # may be None
    for f in parsed['findings']:
        parameter = f['parameter']
        value     = f['value']
        unit      = f.get('unit')              # may be None
        # db.execute('INSERT INTO results ...',
        #            (order_id, machine, parameter, value, unit, body.get('client_ip')))

    return jsonify({'status': 'success', 'saved': len(parsed['findings'])}), 200

if __name__ == '__main__':
    app.run(port=3001)
Configure in LISBridge

Configure in LISBridge

Once your /api/lis-data endpoint is running, point LISBridge at it:

1

Open Settings → Connection

Open Settings in the LISBridge desktop app and go to the Connection section.
2

Enter your Base URL

Paste your server's Base URL in the HIS / LIS Server URL field. For an on-premise lab system use your local network IP and port, e.g. http://192.168.1.100:8080; for a cloud-based system use a domain, e.g. https://lab.example.com. Do not include /api/lis-data — LISBridge appends it.
3

Click Save

LISBridge starts forwarding every future parsed result to {your Base URL}/api/lis-data.
Tip: If the URL is empty, LISBridge skips forwarding and keeps results stored locally. Results received before the URL is set can be re-sent later with the manual forward action.
Note: Running a multi-tenant setup? The request does not include a tenant identifier, so route incoming posts to the correct database using your own mechanism (for example a per-tenant hostname or a reverse-proxy rule).
New in v1.20 · Bidirectional

Order Intake API

The Order Intake API is the reverse direction: your HIS or HMS POSTs a test order to LISBridge, which routes it to the correct analyzer and transmits it via HL7 OML^O33 (MLLP) or ASTM E1394 worklist download. LISBridge reads the ACK and tracks delivery status.

Note: This API is served by the LISBridge desktop app itself — not your server. The device running LISBridge must be reachable from your HIS on the configured port. Enable and configure it under Settings → Bidirectional.

Endpoint

http
POST http://<device-ip>:<port>/api/orders
Content-Type: application/json
X-LISBridge-Token: <your-token>

The port is configurable in LISBridge (any free port, e.g. 8765). The path is always /api/orders. A health check is available at GET http://<device-ip>:<port>/.

Authentication

Every request must include the shared token set in LISBridge Settings. The server fails closed — if no token is configured, all requests return 503.

X-LISBridge-Tokenheaderrequired
Shared token configured in LISBridge Settings → Bidirectional. Accepted as X-LISBridge-Token or Authorization: Bearer <token>.

Request Payload

json
{
  "his_order_id":       "ORD-2026-00142",   // required — idempotency key
  "target_instrument":  "Horiba Yumizen",   // required — instrument name or model key
  "specimen_id":        "S20260628-001",    // required — barcode / sample ID
  "specimen_type":      "BLOOD",            // optional
  "patient_id":         "PA001",            // optional
  "patient_first_name": "John",             // optional
  "patient_last_name":  "Doe",             // optional
  "patient_gender":     "M",               // optional  "M" | "F" | "U"
  "patient_dob":        "19900115",         // optional  YYYYMMDD
  "priority":           "R",               // optional  "R" = routine | "S" = STAT
  "action":             "NW",              // optional  "NW" | "CA" | "RP" (default: NW)
  "referred_by":        "DR001",           // optional
  "profiles": [                            // optional — omit for fixed-panel instruments
    { "code": "CBC",  "name": "CBC" },
    { "code": "DIFF", "name": "Differential" }
  ],
  "max_attempts": 3                        // optional — delivery retry cap (default: 3)
}
his_order_idstringrequired
Idempotency key from your HIS. If an order with this ID is already in the queue, LISBridge returns the existing record instead of creating a duplicate.
target_instrumentstringrequired
Name or model key of the target analyzer. Must match an instrument provisioned in LISBridge. Case-insensitive partial match (e.g. "Yumizen" matches "Horiba Yumizen P8000").
specimen_idstringrequired
Sample barcode or specimen ID sent to the analyzer in the order message.
specimen_typestringoptional
Sample type code (e.g. BLOOD, SERUM, URINE). Passed through to the order message.
patient_id / patient_* / patient_dobstringoptional
Patient demographics included in the order message. patient_dob should be YYYYMMDD.
prioritystringoptional
"R" (routine, default) or "S" (STAT). Passed to the analyzer.
actionstringoptional
Order control: "NW" = new order (default), "CA" = cancel, "RP" = replace.
profilesarrayoptional
Test profile codes for multi-panel analyzers. Each item: { code: string, name?: string }. Omit for fixed-panel instruments (e.g. Yumizen).
max_attemptsintegeroptional
Max delivery attempts before the order is marked failed. Defaults to 3.
Tip: To send an order to multiple instruments (e.g. CBC to Yumizen and chemistry to Pentra C400), POST one request per instrument. Each gets its own his_order_id and is tracked independently.

Response

On success, LISBridge returns 202 Accepted — the order is queued; delivery is asynchronous.

json
// 202 Accepted
{
  "id": 42,
  "his_order_id": "ORD-2026-00142",
  "status": "pending",
  "created": true
}
idintegeroptional
Internal LISBridge order ID.
his_order_idstringoptional
The ID you sent — echoed back for correlation.
statusstringoptional
Initial status: "pending". Transitions to "sent" or "failed" after delivery attempt.
createdbooleanoptional
true if a new order was created; false if an existing order with the same his_order_id was returned.
Error responses
400 Bad RequestInvalid or incomplete request JSON. Error message in { "error": "..." }.
401 UnauthorizedToken missing or incorrect.
404 Not FoundPath is not /api/orders.
503 Service UnavailableToken not configured in LISBridge, or bidirectional feature is disabled.

Code Examples — Posting an Order

Node.js (fetch)

javascript
const order = {
  his_order_id: "ORD-2026-00142",
  target_instrument: "Horiba Yumizen",
  specimen_id: "S20260628-001",
  specimen_type: "BLOOD",
  patient_id: "PA001",
  patient_first_name: "John",
  patient_last_name: "Doe",
  priority: "R",
};

const res = await fetch("http://192.168.1.55:8765/api/orders", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-LISBridge-Token": process.env.LISBRIDGE_TOKEN,
  },
  body: JSON.stringify(order),
});

if (res.status === 202) {
  const ref = await res.json();
  console.log("Order queued:", ref.id, ref.his_order_id);
} else {
  const err = await res.json();
  console.error("Order rejected:", err.error);
}

Python (requests)

python
import requests, os

order = {
    "his_order_id": "ORD-2026-00142",
    "target_instrument": "Horiba Yumizen",
    "specimen_id": "S20260628-001",
    "specimen_type": "BLOOD",
    "patient_id": "PA001",
    "priority": "R",
}

resp = requests.post(
    "http://192.168.1.55:8765/api/orders",
    json=order,
    headers={"X-LISBridge-Token": os.environ["LISBRIDGE_TOKEN"]},
    timeout=10,
)

if resp.status_code == 202:
    ref = resp.json()
    print("Queued:", ref["id"], ref["his_order_id"])
else:
    print("Rejected:", resp.json().get("error"))

PHP (cURL)

php
<?php
$order = [
    'his_order_id'       => 'ORD-2026-00142',
    'target_instrument'  => 'Horiba Yumizen',
    'specimen_id'        => 'S20260628-001',
    'specimen_type'      => 'BLOOD',
    'patient_id'         => 'PA001',
    'priority'           => 'R',
];

$ch = curl_init('http://192.168.1.55:8765/api/orders');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($order),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'X-LISBridge-Token: ' . getenv('LISBRIDGE_TOKEN'),
    ],
]);

$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($code === 202) {
    $ref = json_decode($body, true);
    echo "Queued: " . $ref['id'] . " " . $ref['his_order_id'];
} else {
    echo "Rejected: " . json_decode($body, true)['error'];
}

Configure Order Intake in LISBridge

1

Enable Bidirectional

Open LISBridge → Settings → Bidirectional and toggle on Order Placement.
2

Set the intake port

Enter a free port (e.g. 8765). LISBridge will listen on 0.0.0.0:<port> for incoming orders.
3

Set the intake token

Enter a random secret token. Your HIS must send this exact value in the X-LISBridge-Token header.
4

Provision instruments

Go to Settings → Instruments and verify each bidirectional-capable analyzer is provisioned with its IP/port. Orders are routed by matching target_instrument to the provisioned name.
Important: The Order Intake server starts only when both a port and a token are configured. Without a token it returns 503 for every request — this is intentional (fail-closed security).
New in v1.24 · NAT-friendly

Order Pull API

Order Pull is the alternative to Order Intake for environments where the LISBridge machine is behind NAT or a firewall that blocks inbound connections. Instead of your HIS pushing orders to LISBridge, LISBridge polls your HIS endpoint on a configurable interval and pulls pending orders itself. LISBridge makes the outbound connection — no open ports required on the analyzer PC.

LISBridge (polls)→ POST to your endpoint every N seconds →Your HIS endpoint→ returns pending orders →Delivered to analyzer
Note: Unlike Order Intake (where LISBridge hosts a server), Order Pull requires you to host an endpoint that LISBridge calls. You control the server; LISBridge is the client.

Your endpoint (what LISBridge calls)

You expose a single POST endpoint at any URL you choose. Configure that URL in LISBridge under Settings → HIS/HMS Order Pull (Poll).

http
POST https://your-his.example.com/api/orders/poll
Content-Type: application/json
x-lisbridge-token: <your-secret-token>

Authentication

LISBridge sends the token you configured in the header name you specified. You validate it on your endpoint and reject requests with a wrong or missing token with HTTP 401.

x-lisbridge-tokenheaderrequired
Default header name — configurable in LISBridge. LISBridge also accepts Authorization: Bearer <token> as an alternative if your endpoint prefers that.

Request body — what LISBridge sends each poll

Each poll includes an ack array confirming the orders LISBridge received and processed in the previous cycle. On the very first poll, ack is empty.

json
// POST body LISBridge sends on each poll
{
  "source":    "LISBridge",
  "device_id": "abc123-device-uuid",
  "ack": [
    {
      "his_order_id": "ORD-2026-001",   // the ID you sent in the previous response
      "lis_id":       42,               // LISBridge internal order ID (store for debugging)
      "status":       "recorded",       // "recorded" | "duplicate" | "rejected"
      "error":        null              // non-null only when status = "rejected"
    }
  ]
}
ack[].his_order_idstringoptional
The order ID you returned in the previous response — use this to mark it fulfilled in your HIS.
ack[].lis_idinteger|nulloptional
LISBridge internal ID assigned when the order was first received. Useful for support/debugging.
ack[].statusstringoptional
"recorded" — order accepted and sent to the analyzer. "duplicate" — LISBridge already had this order (idempotent; mark as fulfilled). "rejected" — delivery failed; error field has details.
ack[].errorstring|nulloptional
Present when status is "rejected". Describes the failure reason (e.g. instrument not reachable, unknown target_instrument).

Response — what your endpoint must return

Return HTTP 200 with a JSON body containing the next batch of pending orders. Return an empty array when there are no pending orders — do not return an error.

json
// 200 OK — your endpoint responds with pending orders
{
  "orders": [
    {
      "his_order_id":      "ORD-2026-002",   // required — stable unique ID from your HIS
      "target_instrument": "Sysmex XN-550",  // required — must match an instrument in LISBridge
      "priority":          "ROUTINE",        // optional — "ROUTINE" | "URGENT" (default: ROUTINE)
      "patient": {                           // optional — included in the order message
        "id":     "P-10045",
        "name":   "Ahmed Rahman",
        "age":    34,
        "gender": "M"                        // "M" | "F" | "U"
      },
      "tests": ["CBC", "WBC DIFF"]           // optional — test/profile codes
    }
  ]
}
orders[].his_order_idstringrequired
Your HIS's unique, stable ID for this order. LISBridge uses it as the idempotency key — the same order sent twice will be deduped and confirmed as 'duplicate' in the next poll's ack.
orders[].target_instrumentstringrequired
Name of the analyzer to route the order to. Must match (case-insensitive partial) an instrument provisioned in LISBridge.
orders[].prioritystringoptional
"ROUTINE" (default) or "URGENT". Passed through to the instrument order message.
orders[].patientobjectoptional
Patient demographics included in the order message. All fields optional.
orders[].testsstring[]optional
Test or profile codes included in the order message. Omit for fixed-panel instruments.
Tip: Your endpoint should be idempotent. If LISBridge re-sends an ack for an order already confirmed (e.g. after a crash), ignore it gracefully — do not create a duplicate or return an error. An 'ack' with status 'duplicate' means LISBridge already had that order and deduped it on its side.
Important: Always validate the auth token. Respond with HTTP 401 for a missing or incorrect token. If your endpoint returns non-200 (other than 401), LISBridge logs the error and retries on the next poll cycle.

Minimal implementation — Node.js (Express)

javascript
const express = require("express");
const app = express();
app.use(express.json());

const TOKEN = process.env.LISBRIDGE_PULL_TOKEN; // set this to match LISBridge config

app.post("/api/orders/poll", (req, res) => {
  // 1. Validate token
  if (req.headers["x-lisbridge-token"] !== TOKEN) {
    return res.status(401).json({ error: "unauthorized" });
  }

  // 2. Process acks from the previous poll
  for (const a of req.body.ack ?? []) {
    if (a.status === "recorded" || a.status === "duplicate") {
      db.markOrderFulfilled(a.his_order_id); // your HIS logic here
    } else if (a.status === "rejected") {
      console.error("Order rejected:", a.his_order_id, a.error);
    }
  }

  // 3. Respond with pending orders
  const pending = db.getPendingOrders({ limit: 500 }); // your HIS query here
  res.json({
    orders: pending.map(o => ({
      his_order_id:      o.id,
      target_instrument: o.instrument,
      priority:          o.urgent ? "URGENT" : "ROUTINE",
      patient: { id: o.patientId, name: o.patientName, age: o.age, gender: o.gender },
      tests:             o.testCodes,
    })),
  });
});

app.listen(3000);

Configure Order Pull in LISBridge

1

Open Settings → HIS/HMS Order Pull (Poll)

In LISBridge, go to Settings → HIS/HMS Order Pull (Poll).
2

Enter your endpoint URL

Paste the full URL of your polling endpoint, e.g. https://his.example.com/api/orders/poll.
3

Set the auth header and token

Set Auth Header to the header name your endpoint reads (default: x-lisbridge-token). Set Auth Token to the secret value your endpoint expects.
4

Enable polling and test

Toggle Enable Polling on and click Test Endpoint. LISBridge will send one poll immediately and report the result.
Using LIS Bridge Lab? Your lab dashboard implements this endpoint out of the box. Go to Settings → LIS Desktop, click Generate Token, and copy the URL, header name, and token directly into LISBridge. No custom endpoint development needed.
Support

Need help with your integration?

If you have a question about payload mapping, order routing, instrument configuration, or unexpected behaviour from /api/lis-data, /api/orders, or the Order Pull polling contract, our team can help.

Integration support

Contact us with questions about payload mapping or endpoint configuration and we'll help you get your receiver working.

Contact support →