Lab Reports API Implementation Guide

This guide provides complete implementation details and best practices for integrating the Lab Reports API into your applications. For detailed API specifications and data types, refer to the API Reference.
Authentication: The code examples in this guide focus on the lab report analysis functionality. For authentication implementation details, see the Authentication documentation. You’ll need to add Bearer token into the Authorization header to all API requests.

API Endpoints

The Lab Reports API provides three main endpoints for managing lab reports:

Document Preparation

For optimal document capture guidelines, see the Document Guidelines in the API overview.

POST Request Body

Processing Modes

The processing mode is controlled by the wait_on_process parameter. The API supports two processing modes, each suited for a different use case:

Asynchronous Processing

Returns immediately and processes the document in the background. Ideal for user-facing applications where immediate feedback is important. See the Asynchronous Processing Guide for complete implementation details, webhook configuration, and code examples.

Synchronous Processing

Waits for complete analysis before responding. Best for batch processing, server-to-server integrations, or when you can handle longer response times: Request:
{
  "mime_type": "application/pdf",
  "body": "base64-encoded-document-data",
  "filename": "lab_report_2024_01_15.pdf",
  "wait_on_process": true
}
Response:
{
  "lab_report": {
    "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "status": "completed",
    "collection_date": "2024-01-15",
    "result_date": "2024-01-16",
    "sections": [
      {
        "loinc_code": "58410-2",
        "loinc_common_name": "CBC panel - Blood by Automated count",
        "original_section_name": "HEMATOLOGY",
        "results": [
          {
            "loinc_code": "6690-2",
            "loinc_common_name": "Leukocytes [#/volume] in Blood",
            "original_test_name": "White Blood Count",
            "original_unit_name": "K/uL",
            "standard_unit_name": "K/uL",
            "value": 7.2,
            "normal_min": 4.0,
            "normal_max": 11.0,
            "within_normal_range": true,
            "require_human_review": false
          }
        ]
      }
    ],
    "uploaded_at": "2024-01-15T10:30:04.521Z",
    "modified_at": "2024-01-15T10:30:12.132Z"
  }
}

Response Body

Processing Status

Analysis of the lab report progresses through these states:
  • pending — Analysis has been queued
  • processing — AI model is actively analyzing the document
  • completed — Analysis finished successfully with results
  • failed — Processing failed due to unreadable content or technical issues
REST API responses and webhook notifications will always include the status field.
{
  "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "status": "processing",
  "uploaded_at": "2024-01-15T10:30:04.521Z"
}
Webhook notifications can have status field with values completed or failed only. Always handle different response statuses in your application logic.

Error Scenarios

Processing Failures

These occur when the API successfully receives your request but the AI analysis fails. The response will have HTTP 200 status with "status": "failed". When status is failed, check the parsing_error field for specific details:
  • Document not a lab report: Document contains non-medical content
  • Poor document quality: Blurry, dark, or low-resolution images/scans
  • Unreadable text: OCR could not extract reliable text

Request Errors

These occur when there’s an issue with the request itself, returning non-200 HTTP status codes before analysis begins. For HTTP status code errors (400, 401, 422, etc.) and general API error handling, see the Error Handling documentation.

Retrieving Results

Getting Results

For asynchronous processing, use webhooks for real-time notifications. See the Asynchronous Processing Guide for complete webhook implementation details. If webhooks are not yet available, you can check status using the GET /lab_reports/{lab_report_id} endpoint.

Best Practices

1. Choose the Right Processing Mode

  • Asynchronous: For user-facing applications, mobile apps, and when you want immediate feedback
  • Synchronous: For batch processing, server-to-server integrations, and when you can handle longer response times

2. Implement Proper Error Handling

  • Handle network errors gracefully
  • Provide meaningful error messages to users
  • Implement retry logic for transient failures

3. Optimize Document Upload

  • Use high-resolution scans (300+ DPI) for better OCR results
  • Validate document size and format before upload
  • Show upload progress for large documents

Code Examples

Here are complete implementation examples in multiple programming languages:
import os
import requests
import base64
import hmac
import hashlib
import json

SPIKE_API_BASE_URL = os.getenv("SPIKE_API_BASE_URL")
SPIKE_APP_HMAC_KEY = os.getenv("SPIKE_APP_HMAC_KEY")

try:
    SPIKE_APP_ID = int(os.getenv("SPIKE_APP_ID"))
except (ValueError, TypeError):
    raise ValueError("SPIKE_APP_ID must be an integer.")

# Implementation depends on your authentication method
# See: /api-docs/authentication for complete details
class SpikeAuth:
    def __init__(self, end_user_id):
        self.user_id = end_user_id
        self.app_id = SPIKE_APP_ID
        self.hmac_key = SPIKE_APP_HMAC_KEY
        self.base_url = SPIKE_API_BASE_URL
        self.hmac_signature = self._generate_hmac_signature()
        self.access_token = None

    def _generate_hmac_signature(self):
        h = hmac.new(self.hmac_key.encode('utf-8'), self.user_id.encode('utf-8'), hashlib.sha256)
        return h.hexdigest()

    def get_bearer_token(self) -> str:
        if self.access_token is not None:
            return self.access_token
        else:
            body = {
                "application_id": self.app_id,
                "application_user_id": self.user_id,
                "signature": self.hmac_signature
            }

            response = requests.post(
                f"{self.base_url}/auth/hmac",
                json=body,
                headers={"Content-Type": "application/json", "Accept": "application/json"},
                timeout=20
            )

            if response.status_code == 200:
                try:
                    print(f"Successfully obtained Bearer token for {self.user_id}")
                    self.access_token = response.json()["access_token"]
                    return self.access_token
                except (KeyError, json.JSONDecodeError) as e:
                    print(f"Failed to parse access token from successful response for {self.user_id}: {e}")
                raise requests.RequestException(f"Token parsing failed for {self.user_id}")
            else:
                raise Exception(f"Authentication failed for {self.user_id} with status {response.status_code}")

class LabReportsAPI:
    def __init__(self, auth: SpikeAuth):
        self.spike_auth = auth

    def _create_auth_headers(self, method="GET") -> dict[str, str]:
        """Create authentication headers - see authentication docs for details"""
        try:
            token = self.spike_auth.get_bearer_token()
            req_headers = {
                "Authorization": f"Bearer {token}",
                "Accept": "application/json"
            }
            if method == "POST":
                req_headers["Content-Type"] = "application/json"
            return req_headers
        except requests.RequestException as auth_error:
            print(f"Failed to get bearer token for Lab Reports API request header: {auth_error}")
        raise

    def upload_lab_report(self, document_path, **options):
        """Upload a lab report document for analysis"""
        # Read and encode document
        try:
            with open(document_path, 'rb') as doc_file:
                document_data = base64.b64encode(doc_file.read()).decode('utf-8')
        except FileNotFoundError:
            print(f"Document file not found: {document_path}")
            return None

        # Prepare the request body
        body = {
            "body": document_data,
            "mime_type": options.get("mime_type", "application/pdf"),
            "filename": options.get("filename", "lab_report.pdf"),
            "wait_on_process": options.get("wait_on_process", False)
        }

        # Create request body and headers
        body_json = json.dumps(body)
        headers = self._create_auth_headers(method="POST")

        # Make request
        response = requests.post(
            f"{SPIKE_API_BASE_URL}/lab_reports",
            headers=headers,
            data=body_json
        )
        
        if response.status_code == 200:
            return response.json()

        print(f"Response status: {response.status_code}")
        return None

# Example usage
user_id = "lab_reports_test_user"
spike_auth = SpikeAuth(user_id)
api = LabReportsAPI(spike_auth)

# Upload document asynchronously (recommended)
result = api.upload_lab_report("/path/to/lab_report.pdf", wait_on_process=False)

if result:
    print(f"Status: {result['lab_report']['status']}")
    print(f"Record ID: {result['lab_report']['record_id']}")

# For synchronous processing (wait for results)
result = api.upload_lab_report("/path/to/lab_report.pdf", wait_on_process=True)

if result and result['lab_report']['status'] == 'completed':
    lab_report = result['lab_report']
    print(f"Analysis completed for {lab_report['record_id']}")
    print(f"Collection Date: {lab_report.get('collection_date', 'N/A')}")
    print(f"Result Date: {lab_report.get('result_date', 'N/A')}")
    
    if lab_report.get('sections'):
        print(f"Found {len(lab_report['sections'])} sections")
        for section in lab_report['sections']:
            print(f"- Section: {section.get('original_section_name', 'Unknown')}")
            for result in section.get('results', []):
                value = result.get('value') or result.get('value_text', 'N/A')
                unit = result.get('original_unit_name', '')
                normal_status = ""
                if result.get('within_normal_range') is not None:
                    normal_status = " (Normal)" if result['within_normal_range'] else " (Abnormal)"
                print(f"  + {result.get('original_test_name')}: {value} {unit}{normal_status}")
For complete API specification, data types, and additional parameters, see the POST /lab_reports API Reference.