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
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.
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>.
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 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.
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.