Prolab LIS – Middleware Integration API Documentation
Overview
This document describes the API integration between Prolab LIS, Middleware, and Laboratory Analyzers. The middleware acts as a bridge, receiving pending test orders from Prolab LIS and forwarding analyzer results back to the system.
Integration Flow
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Prolab LIS │ ◄─────► │ Middleware │ ◄─────► │ Analyzer │
│ │ │ │ │ │
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
│ 1. Get Pending Orders │ │
│ ◄──────────────────────┘ │
│ │ │
│ │ 2. Send to Analyzer │
│ │ ──────────────────────►│
│ │ │
│ │ 3. Receive Results │
│ │ ◄──────────────────────│
│ │ │
│ 4. Update Test Results │ │
│ ◄───────────────────────┘ │
Authentication
All API endpoints require JWT authentication. The middleware must authenticate before making any requests.
Endpoint
POST /dotnet_api/v1/auth/signin/
Request
{
"email": "[email protected]",
"password": "your-password"
}
Response
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"email": "[email protected]",
"roles": ["SuperUser"],
"businessOrganisationId": "4fd0a57b-2789-4e43-a6af-9c263f258a6f"
}
Usage
Include the token in the Authorization header for all subsequent requests:
Authorization: Bearer <token>
Token Expiration
Tokens expire after a set period. The middleware should implement token refresh logic to handle expired tokens gracefully.
Getting Pending Test Orders
The middleware needs to periodically poll Prolab LIS to retrieve pending test orders that need to be sent to analyzers.
Endpoint
GET /dotnet_api/v1/testresult/all
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
filter | string | Yes | Filter field name (use status) |
query | string | Yes | Filter value (use Pending) |
page | integer | No | Page number (default: 1) |
limit | integer | No | Results per page (default: 10, max: 100) |
sortBy | string | No | Sort field (default: CreatedAt) |
sortOrder | string | No | Sort direction: ASC or DESC (default: DESC) |
Request Example
GET /dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=50&sortBy=CreatedAt&sortOrder=ASC
Authorization: Bearer <token>
Response Structure
{
"items": [
{
"id": "4310beb1-909e-4b5d-bad3-7748bd62cf71",
"barcode": "1202463241407961",
"orderCode": "ORD63241410P6G4",
"orderNumber": "WO000000900955",
"testId": "11223344-5566-7788-99aa-bbccddeeff00",
"testName": "Cholésterol total",
"testCode": "CHOT",
"testAlias": "CHOL-T",
"testPanel": "Lipid Panel",
"result": "",
"status": "Pending",
"comments": "",
"resultDate": "2025-11-06T09:00:00Z",
"verifiedDate": "0001-01-01T00:00:00Z",
"createdAt": "2025-11-06T09:00:00Z",
"updatedAt": "2025-11-06T09:00:00Z"
}
],
"totalCount": 25,
"currentPage": 1,
"totalPages": 1,
"statistics": {
"totalCount": 25,
"statusCounts": [
{
"status": "Pending",
"count": 25
}
]
}
}
Key Fields for Middleware
barcode: Unique barcode identifier for the sample (use for barcode endpoint)orderCode: Order code identifier (use for orderCode endpoint)testCode: Test code to identify which test to updatetestAlias: Alternative test identifier (can be used instead of testCode)testName: Human-readable test namestatus: Current status (should be “Pending” for new orders)
Polling Strategy
Recommended Approach:
- Poll every 30-60 seconds for new pending orders
- Track processed orders to avoid duplicates
- Use pagination to handle large batches
- Filter by date range if needed:
startDateandendDateparameters
Example Polling Request:
# Get pending orders from the last hour
GET /dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=100&sortBy=CreatedAt&sortOrder=ASC&startDate=2025-11-06T08:00:00Z&endDate=2025-11-06T09:00:00Z
Sending Analyzer Results
After receiving results from the analyzer, the middleware sends them back to Prolab LIS using one of two endpoints. Both endpoints accept an array of test results, allowing batch updates.
Endpoint 1: Update by Barcode
PATCH /dotnet_api/v1/testresult/barcode/{barcode}
Use this endpoint when you have the sample barcode from the analyzer.
Endpoint 2: Update by Order Code
PATCH /dotnet_api/v1/testresult/ordercode/{orderCode}
Use this endpoint when you have the order code identifier.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
barcode | string | Yes* | Sample barcode identifier |
orderCode | string | Yes* | Order code identifier |
*Use either barcode OR orderCode, not both.
Request Body
The request body is an array of test result objects. You can send a single test or multiple tests in one request.
[
{
"testCode": "CHOT",
"result": "5.8",
"secondaryResult": "580",
"secondaryResultUnit": "mg/dL",
"status": "Completed",
"comments": "Analyzer result - within normal range",
"interpretation": "Normal",
"resultDate": "2025-11-06T10:30:00Z",
"verifiedDate": "2025-11-06T10:30:00Z",
"verifiedBy": "Analyzer-001"
},
{
"testCode": "TG",
"result": "1.65",
"status": "Completed",
"comments": "Normal triglycerides",
"resultDate": "2025-11-06T10:30:00Z"
}
]
Request Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
testCode | string | Yes* | Test code identifier |
testAlias | string | Yes* | Alternative test identifier (use if testCode not available) |
result | string | No | Primary test result value |
secondaryResult | string | No | Secondary result value (e.g., converted units) |
secondaryResultUnit | string | No | Unit for secondary result |
status | string | No | Test status: Pending, Completed, Verified, Cancelled |
comments | string | No | Additional comments or notes |
interpretation | string | No | Test interpretation |
resultDate | datetime | No | Date/time when result was obtained (ISO 8601 format) |
verifiedDate | datetime | No | Date/time when result was verified (ISO 8601 format) |
verifiedBy | string | No | User/system that verified the result |
*Either testCode OR testAlias must be provided to identify which test to update.
Response
Success Response (200 OK):
{
"success": true,
"message": "Test results updated successfully"
}
Error Response (400 Bad Request):
{
"success": false,
"message": "Failed to update test results"
}
Error Response (404 Not Found):
{
"success": false,
"message": "Test result with barcode 'BC123456789' not found"
}
Error Response (500 Internal Server Error):
{
"success": false,
"message": "An error occurred while updating test results"
}
Complete Request Examples
Example 1: Update by Barcode (Single Test)
PATCH /dotnet_api/v1/testresult/barcode/1202463241407961
Authorization: Bearer <token>
Content-Type: application/json
[
{
"testCode": "CHOT",
"result": "5.8",
"status": "Completed",
"comments": "Analyzer result"
}
]
Example 2: Update by Barcode (Multiple Tests)
PATCH /dotnet_api/v1/testresult/barcode/1202463241407961
Authorization: Bearer <token>
Content-Type: application/json
[
{
"testCode": "CHOT",
"result": "5.8",
"secondaryResult": "580",
"secondaryResultUnit": "mg/dL",
"status": "Completed",
"comments": "Normal range"
},
{
"testCode": "TG",
"result": "1.65",
"status": "Completed",
"comments": "Normal triglycerides"
},
{
"testAlias": "HDL",
"result": "1.4",
"status": "Completed"
}
]
Example 3: Update by Order Code
PATCH /dotnet_api/v1/testresult/ordercode/ORD63241410P6G4
Authorization: Bearer <token>
Content-Type: application/json
[
{
"testCode": "MCYT",
"result": "0.5",
"status": "Completed",
"comments": "Monocytes count normal"
},
{
"testCode": "LYT",
"result": "2.1",
"status": "Completed",
"comments": "Lymphocyte count within range"
}
]
Test Identification Logic
The system uses the following logic to identify which test result to update:
- Filter by barcode/orderCode: First, filter test results by the provided barcode or orderCode
- Filter by testCode or testAlias:
- If
testCodeis provided, match againstTest.Code - If
testAliasis provided (andtestCodeis not), match againstTest.Alias
- If
- Update: Update the first matching test result
Important: If multiple test results match (same barcode/orderCode and testCode), only the first one will be updated. Ensure your test codes are unique per sample.
Error Handling
Common Error Scenarios
1. Authentication Errors
401 Unauthorized
{
"detail": "Unauthorized"
}
Solution: Re-authenticate and obtain a new token.
2. Test Not Found
404 Not Found
{
"success": false,
"message": "Test result with barcode 'BC123456789' not found"
}
Possible Causes:
- Barcode/orderCode doesn’t exist
- Test code/alias doesn’t match any pending test
- Test result was already processed
Solution: Verify the barcode/orderCode and testCode are correct. Check if the test was already completed.
3. Validation Errors
400 Bad Request
{
"success": false,
"message": "Failed to update test results"
}
Possible Causes:
- Missing required fields
- Invalid data format
- Test result not found
Solution: Verify request body structure and required fields.
4. Server Errors
500 Internal Server Error
{
"success": false,
"message": "An error occurred while updating test results"
}
Solution: Retry the request. If the error persists, check server logs.
Retry Strategy
Recommended Retry Logic:
- Immediate Retry: Retry once immediately for transient errors (network issues)
- Exponential Backoff: For persistent errors, wait 1s, 2s, 4s before retrying
- Max Retries: Limit to 3 retries before logging as failed
- Dead Letter Queue: Store failed requests for manual review
Example Retry Implementation (Pseudo-code):
async function sendResultsWithRetry(endpoint, data, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(endpoint, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
return await response.json();
}
// Don't retry on 404 (not found) or 400 (bad request)
if (response.status === 404 || response.status === 400) {
throw new Error(`Client error: ${response.status}`);
}
// Retry on 500 or network errors
if (attempt < maxRetries - 1) {
await sleep(Math.pow(2, attempt) * 1000); // Exponential backoff
}
} catch (error) {
if (attempt === maxRetries - 1) {
throw error; // Final attempt failed
}
await sleep(Math.pow(2, attempt) * 1000);
}
}
}
Best Practices
1. Authentication
- Store tokens securely: Never log or expose tokens
- Implement token refresh: Handle token expiration gracefully
- Use service accounts: Create dedicated service accounts for middleware integration
- Rotate credentials: Regularly rotate API credentials
2. Polling for Pending Orders
- Poll frequency: Poll every 30-60 seconds (adjust based on volume)
- Track processed orders: Maintain a list of processed order IDs to avoid duplicates
- Use date filters: Filter by date range to only get recent orders
- Handle pagination: Process all pages of results
- Idempotency: Ensure the same order isn’t processed twice
3. Sending Results
- Batch updates: Send multiple test results in a single request when possible
- Validate data: Validate analyzer results before sending to Prolab LIS
- Include timestamps: Always include
resultDateandverifiedDate - Error handling: Implement robust error handling and retry logic
- Logging: Log all requests and responses for debugging
4. Data Mapping
- Test Code Mapping: Maintain a mapping table between analyzer test codes and Prolab LIS test codes
- Unit Conversion: Handle unit conversions if analyzer uses different units
- Value Formatting: Ensure numeric values are formatted correctly (string format)
- Status Mapping: Map analyzer statuses to Prolab LIS statuses:
Pending→ New order, not yet processedCompleted→ Result availableVerified→ Result verified by technicianCancelled→ Test cancelled
5. Performance
- Connection pooling: Reuse HTTP connections
- Async processing: Process multiple orders in parallel
- Rate limiting: Respect API rate limits
- Batch size: Send results in batches of 10-50 tests per request
6. Monitoring
- Health checks: Monitor middleware health and API connectivity
- Metrics: Track:
- Number of pending orders retrieved
- Number of results sent
- Success/failure rates
- Average processing time
- Alerts: Set up alerts for:
- High error rates
- Authentication failures
- Processing delays
Complete Example Flow
Scenario: Processing a Lipid Panel Order
Step 1: Authenticate
POST /dotnet_api/v1/auth/signin/
Content-Type: application/json
{
"email": "[email protected]",
"password": "secure-password"
}
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"email": "[email protected]"
}
Step 2: Get Pending Orders
GET /dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=50
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response:
{
"items": [
{
"barcode": "1202463241407961",
"orderCode": "ORD63241410P6G4",
"testCode": "CHOT",
"testName": "Cholésterol total",
"status": "Pending"
},
{
"barcode": "1202463241407961",
"orderCode": "ORD63241410P6G4",
"testCode": "TG",
"testName": "Triglycerides",
"status": "Pending"
}
],
"totalCount": 2
}
Step 3: Send to Analyzer
The middleware formats the order and sends it to the analyzer (implementation depends on analyzer protocol).
Example Analyzer Request:
Barcode: 1202463241407961
Tests: CHOT, TG
Step 4: Receive Analyzer Results
The analyzer returns results:
Barcode: 1202463241407961
CHOT: 5.8 mmol/L
TG: 1.65 mmol/L
Step 5: Send Results to Prolab LIS
Option A: Using Barcode
PATCH /dotnet_api/v1/testresult/barcode/1202463241407961
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
[
{
"testCode": "CHOT",
"result": "5.8",
"status": "Completed",
"comments": "Analyzer: Auto-analyzer-001",
"resultDate": "2025-11-06T10:30:00Z",
"verifiedDate": "2025-11-06T10:30:00Z"
},
{
"testCode": "TG",
"result": "1.65",
"status": "Completed",
"comments": "Analyzer: Auto-analyzer-001",
"resultDate": "2025-11-06T10:30:00Z",
"verifiedDate": "2025-11-06T10:30:00Z"
}
]
Option B: Using Order Code
PATCH /dotnet_api/v1/testresult/ordercode/ORD63241410P6G4
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
[
{
"testCode": "CHOT",
"result": "5.8",
"status": "Completed",
"comments": "Analyzer: Auto-analyzer-001",
"resultDate": "2025-11-06T10:30:00Z"
},
{
"testCode": "TG",
"result": "1.65",
"status": "Completed",
"comments": "Analyzer: Auto-analyzer-001",
"resultDate": "2025-11-06T10:30:00Z"
}
]
Response:
{
"success": true,
"message": "Test results updated successfully"
}
Step 6: Verify Update
GET /dotnet_api/v1/testresult/all?filter=barcode&query=1202463241407961
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response:
{
"items": [
{
"testCode": "CHOT",
"result": "5.8",
"status": "Completed",
"comments": "Analyzer: Auto-analyzer-001"
},
{
"testCode": "TG",
"result": "1.65",
"status": "Completed",
"comments": "Analyzer: Auto-analyzer-001"
}
]
}
Middleware Implementation Example
Flow Structure
[HTTP Request: Get Pending Orders]
↓
[Parse JSON]
↓
[Filter & Group by Barcode]
↓
[Send to Analyzer]
↓
[Receive Analyzer Results]
↓
[Format Results]
↓
[HTTP Request: Update Results]
↓
[Handle Response]
Example Implementation
1. Get Pending Orders
HTTP Request Configuration:
- Method: GET
- URL:
http://localhost:8081/dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=50 - Headers:
{
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
2. Update Results
HTTP Request Configuration:
- Method: PATCH
- URL:
http://localhost:8081/dotnet_api/v1/testresult/barcode/{barcode} - Headers:
{
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
Example Data Processing:
// Group results by barcode
const results = response.items;
const grouped = {};
results.forEach(item => {
if (!grouped[item.barcode]) {
grouped[item.barcode] = [];
}
grouped[item.barcode].push({
testCode: item.testCode,
result: item.analyzerResult, // From analyzer
status: "Completed",
comments: "From analyzer",
resultDate: new Date().toISOString()
});
});
// Process each barcode group
Object.keys(grouped).forEach(barcode => {
sendUpdateRequest(barcode, grouped[barcode]);
});







