🔒

Lab Integration Guide

Diagnostic.ly · Password required
Wrong password. Try again.

Lab Integration Guide

Complete API integration guide for lab partners. Covers authentication, orders, users, test results, webhooks, and end-to-end examples.

Diagnostic.ly API Integration Guide

Lab Partner Integration Bible

Version: 1.0

Last Updated: February 16, 2026

Base URL: https://api.diagnostic.ly/api

API Documentation (Swagger): https://api.diagnostic.ly/docs


Table of Contents


1. Overview

Diagnostic.ly (GDT - Global Diagnostic Technology) is a healthcare diagnostics orchestration platform. It manages the full lifecycle of diagnostic testing:

Order Placement -> Kit Shipment -> Specimen Collection -> Lab Processing -> Result Reporting -> Provider Review

As a lab partner, you will:

  • Place orders for diagnostic test kits on behalf of patients
  • Register patients in the system
  • Track order status through the fulfillment pipeline
  • Submit test results back into the platform via webhooks
  • Receive status updates via outbound webhooks

2. Getting Started

Prerequisites

Before you can make API calls, you need the following (provisioned by Diagnostic.ly staff):

ItemDescriptionExample
API KeyYour unique authentication keysk_live_abc123...
Account IDYour account identifier (one or both)external_id: "LAB-001" or gdt_id: 42
Permitted MethodsList of API methods enabled for your accountcreateOrder, getUsersList, getTestResults
Bundle SKUsSKUs for the test bundles available to youBDL-STI-001, BDL-COVID-002
Webhook URL (optional)Your endpoint for receiving outbound status updateshttps://yourlab.com/webhooks/diagnostic
Webhook Key (optional)Bearer token Diagnostic.ly will use when calling your webhookwh_key_xyz789...

Base Configuration

Base URL:     https://api.diagnostic.ly/api
Content-Type: application/json
Max Payload:  50 MB

3. Authentication

How It Works

Every API request must include two pieces of identification:

  • API Key – sent as a custom HTTP header
  • Account ID – sent in the request body, query string, or URL parameter

Headers

POST /api/order HTTP/1.1
Host: api.diagnostic.ly
Content-Type: application/json
api-key: YOUR_API_KEY_HERE

Important: The header name is lowercase api-key (with a hyphen).

Account ID Object

The account_id field is an object. You must provide at least one of:

{
  "account_id": {
    "external_id": "YOUR-EXTERNAL-ID",
    "gdt_id": 42
  }
}
FieldTypeMax LengthDescription
external_idstring64 charsYour system’s identifier for the account (alphanumeric)
gdt_idnumberDiagnostic.ly’s internal integer ID

You may provide both, but at least one is required.

Authentication Flow (What Happens Server-Side)

  • API key is extracted from the api-key header and trimmed
  • Account ID is resolved from the request
  • The system looks up your account and matches the API key against the inbound_api_key stored in support_entity_credentials
  • Validates: account is active, support entity is active, account-support entity linkage is active
  • Checks that the specific API method you’re calling is permitted for your account

Authentication Errors

ScenarioStatusResponse
Missing API key400{"type": "ClientError", "statusCode": 400, "message": "API key is required"}
Missing account ID400{"type": "ClientError", "statusCode": 400, "message": "Account id is required"}
Account not found400{"type": "ClientError", "statusCode": 400, "message": "Account (X) not found or not linked with any support entity."}
Inactive account400{"type": "ClientError", "statusCode": 400, "message": "Account (X) is not active."}
Inactive support entity400{"type": "ClientError", "statusCode": 400, "message": "Support entity (X) is not active."}
Invalid API key400{"type": "ClientError", "statusCode": 400, "message": "Invalid API key"}
Method not permitted400{"type": "ClientError", "statusCode": 400, "message": "Invalid api method"}

4. Common Patterns

ID Object Pattern

Many resources use the same ID object pattern for identification:

{
  "external_id": "string (max 64 chars)",
  "gdt_id": 123
}

At least one of the two fields must be provided. This pattern appears in account_id, user_id, group_id, etc.

Date Formats

ContextFormatExample
Date of Birth (DOB)YYYY-MM-DD"1990-05-15"
API response datesMM/DD/YYYY"02/16/2026"
Date-time filterYYYY-MM-DD HH:mm:ss"2026-02-16 14:30:00"
Date filter (short)YYYY-MM-DD"2026-02-16"
ISO fullYYYY-MM-DDTHH:mm:ss.sssZ"2026-02-16T14:30:00.000Z"

Phone Number Format

All phone numbers must include the country code prefix:

Pattern: ^\+\d{10,15}$
Example: "+12125551234"

Standard Success Response Shape

{
  "statusCode": 200,
  "message": "Descriptive success message",
  "data": { ... }
}

Standard Error Response Shape

{
  "type": "ClientError",
  "statusCode": 400,
  "message": "Descriptive error message"
}

5. API Reference: Users

5.1 Create User

POST /api/user

Registers a new patient or staff member in the system.

Request Body

FieldTypeRequiredValidationNotes
account_idobjectYES{ external_id?, gdt_id? } – at least oneYour account identifier
external_idstringNoMax 64, alphanumeric (/^[a-zA-Z0-9\s]+$/)Your system’s ID for this user
namestringYESMax 64, alphanumeric, trimmedPatient’s first name
emailstringYESValid email, max 64Patient’s email address
activebooleanYEStrue or falseWhether user is active
country_codestringYES2-3 chars, non-numeric (ISO2 or ISO3)e.g., "US", "USA"
phone_numberstringYESValidated against country_codee.g., "+12125551234"
timezone_supportnumberYES1 or 2Timezone preference
timezone_idstringNoPattern: /^[\w/-]+$/, allows emptye.g., "America/New_York"
language_idnumberNoIntegerLanguage preference ID
user_typenumberYESSee enum belowDetermines available fields
rolenumberNo1-10Contact type ID
communication_preferencesnumberNo0 or 1Communication opt-in

user_type Values

ValueMeaningExtra Required Fields
1PatientUnlocks: dob, gender, race, ethnicity, group_name, custom_attributes
2Account Admin--
3Observerapproved_bundles_for_observation (array of bundle IDs)
5Managermanager_privilege (array of integers 1-10)
7Providerlicense_id, credentials (both required). Unlocks: permit_provider_ , suppress_provider_ booleans
8Medical Stafflicense_id, credentials (both required)
10Support Entity Managermanager_privilege (array, values: 2, 11)

Patient-Only Fields (when user_type = 1)

FieldTypeRequiredValidation
dobdateNoISO date, stored as YYYY-MM-DD
genderstringNo"Male" or "Female"
gender_identitystringNoMax 100 chars, trimmed
racestringNoValidated against account’s allowed options
ethnicitystringNoValidated against account’s allowed options
group_namestringNoMust exist in system, trimmed
custom_attributesobjectNoKeys: string max 100. Values: string max 255. Max 250 pairs.

Provider-Only Fields (when user_type = 7)

FieldTypeRequired
license_idstringYES (alphanumeric, max 30) – NPI number
credentialsstringYES (alphanumeric, max 30)
permit_provider_to_manage_assigned_patientsbooleanNo
permit_provider_add_and_manage_new_patientsbooleanNo
suppress_provider_access_to_test_resultsbooleanNo
suppress_provider_access_to_patient_device_databooleanNo

Sample Request (Patient)

{
  "account_id": { "external_id": "LAB-001" },
  "external_id": "PAT-12345",
  "name": "John Doe",
  "email": "john.doe@example.com",
  "active": true,
  "country_code": "US",
  "phone_number": "+12125551234",
  "timezone_support": 1,
  "timezone_id": "America/New_York",
  "user_type": 1,
  "dob": "1990-05-15",
  "gender": "Male",
  "gender_identity": "Man",
  "race": "White",
  "ethnicity": "Not Hispanic or Latino",
  "communication_preferences": 1,
  "custom_attributes": {
    "department": "Engineering",
    "employee_id": "E12345"
  }
}

Sample Response

{
  "message": "User created successfully!!!",
  "data": {
    "gdt_id": 456,
    "external_id": "PAT-12345",
    "created_at": "02/16/2026"
  }
}

Note: Duplicate users (by email within the same account) will be rejected.


5.2 Update User

PUT /api/user/:id

Updates an existing user. The :id param is the user’s gdt_id or external_id.

All fields from the create schema are accepted but all are optional except account_id. The same conditional logic for user_type-dependent fields applies.

Sample Request

{
  "account_id": { "external_id": "LAB-001" },
  "name": "John Updated",
  "phone_number": "+13105559876",
  "active": true
}

Sample Response

{
  "message": "User updated successfully!!!",
  "data": {
    "gdt_id": 456,
    "external_id": "PAT-12345",
    "updated_at": "02/16/2026"
  }
}

5.3 List Users

GET /api/user?account_id=YOUR_ACCOUNT_ID

Returns all users for your account.

Note: No pagination is implemented. All users are returned at once.

Sample Response

{
  "message": "Users fetched successfully!!!",
  "data": [
    {
      "account_id": {
        "internal_id": 42,
        "external_id": "LAB-001"
      },
      "user_id": {
        "internal_id": 456,
        "external_id": "PAT-12345"
      },
      "email": "john.doe@example.com",
      "name": "John",
      "last_name": "Doe",
      "dob": "1990-05-15",
      "gender": "Male",
      "race": "White",
      "ethnicity": "Not Hispanic or Latino",
      "gender_identity": "Man",
      "phone_number": "+12125551234",
      "active": true,
      "account_active": 1
    }
  ]
}

5.4 Delete User

DELETE /api/user

Soft-deletes a user (sets is_deleted = 1, active = 0).

Request Body

{
  "account_id": { "external_id": "LAB-001" },
  "user": {
    "gdt_id": 456,
    "external_id": "PAT-12345",
    "email": "john.doe@example.com"
  }
}

The user object requires at least one of gdt_id, external_id, or email.

Sample Response

{
  "message": "User with 456 deleted successfully!!!"
}

6. API Reference: Orders

6.1 Create Order

POST /api/order

Places a new diagnostic test order. This is the primary integration endpoint.

Request Body – Top Level

FieldTypeRequiredDefaultValidation
account_idobjectYES{ external_id?, gdt_id? } – at least one
order_typenumberYES1, 2, or 3 (see enum below)
bundlesarrayYESArray of bundle objects (min 1)
ship_toobjectYESRecipient/patient information
preassign_recipient_as_assigneestringNo"yes""yes" or "no"
payment_responsibilitystringNo"ordering_entity""ordering_entity", "patient", "shared", or null
ordering_entity_infoobjectNoProvider/ordering entity details
language_preferencestringNoTwo-digit ISO 639-1 code (e.g., "en", "es")
custom_attributesobjectNoKey-value pairs (both strings)
external_shipment_informationobjectNoIf you handle your own shipping
supplemental_order_dataobjectNoEligibility and behavioral Q&A

order_type Values

ValueNameDescription
1ShipmentStandard order – test kit is shipped to patient
2Hand to PatientKit is handed directly to patient (no shipment created)
3RegistrationRegistration-only order (no physical kit)

bundles[] – Array of Bundle Objects

Each order must include at least one bundle.

FieldTypeRequiredValidationNotes
skustringOne ofBundle SKU (from your provisioned list)
external_bundle_idstringOne ofVendor bundle ID (alternative to SKU)
quantitynumberYESMin: 1Number of kits to order
flavor_idnumberYES1, 2, or 3See flavor enum below
diagnosis_codestringNoAllows emptyICD-10 diagnosis code (e.g., "Z11.3")
billing_codestringNoAllows emptyCPT billing code (e.g., "87491")
submit_to_insurancestringNo"Yes" or "No"

Important: At least one of sku or external_bundle_id must be provided per bundle.

flavor_id Values

ValueNameDescription
1TEST_ONLYTest kit only
2TEST_AND_EDUCATIONTest kit + educational content
3EDUCATION_ONLYEducational/observation content only

ship_to – Recipient Object

FieldTypeRequiredValidation
first_namestringYES
last_namestringYES
emailstringYESValid email
phonestringYESPattern: ^\+\d{10,15}$
dobdateNoISO date format
genderstringNo"Male", "Female", "Others" (case-insensitive)
gender_identitystringNo
racestringNo
ethnicitystringNo
addressobjectYESSee address fields below

ship_to.address – Address Object

FieldTypeRequiredValidation
street_1stringYES
street_2stringNoAllows null and empty string
citystringYES
regionstringYESState/province code (e.g., "CA", "NY")
postal_codestringYES
countrystringYESExactly 2 characters (ISO 3166-1 alpha-2, e.g., "US")

ordering_entity_info – Ordering Provider (Optional)

FieldTypeRequiredValidation
full_namestringNoAllows empty
ordered_by_entity_namestringNoAllows empty
ordered_by_idstringNoAllows empty (NPI or internal ID)
ordered_by_emailstringNoValid email, allows empty
ordered_by_phonestringNoPattern: ^\+\d{10,15}$, allows empty

payment_responsibility and Shared Payment Fields

ValueDescriptionAdditional Fields
"ordering_entity"Lab/ordering entity pays (default)None
"patient"Patient paysNone
"shared"Split paymentMust provide exactly ONE of the fields below
nullNo payment infoNone

When payment_responsibility = "shared":

FieldTypeNotes
shared_responsibility_patient_copaystringFixed copay amount. Mutually exclusive with percentage.
shared_responsibility_patient_percentagestringPercentage patient pays. Mutually exclusive with copay.
shared_responsibility_patient_nottoexceedstringMaximum patient amount. Only allowed with percentage (forbidden with copay).

external_shipment_information (Optional)

Use this if your lab handles its own shipping instead of using Diagnostic.ly’s fulfillment.

FieldTypeRequiredValidation
shipment_handled_externallystringYES (within this object)"Y", "Yes", "yes", "N", "No", "no"
carrier_idstringConditionalMax 50 chars. Required if shipment_handled_externally is "Y"/"Yes"/"yes"
tracking_idstringConditionalMax 50 chars. Required if shipment_handled_externally is "Y"/"Yes"/"yes"

supplemental_order_data (Optional)

FieldTypeDescription
eligibility_qaarrayArray of eligibility Q&A objects
behavioral_qaarrayArray of behavioral Q&A objects

Each Q&A object:

{
  "question": "Q1",
  "questiontext": "Are you over 18?",
  "answertext": "Yes"
}

custom_attributes (Optional)

Arbitrary key-value metadata attached to the order:

{
  "custom_attributes": {
    "referral_source": "provider_portal",
    "internal_case_id": "CASE-2024-001",
    "notes": "Priority patient"
  }
}

Both keys and values must be strings.


Full Sample: Create Order Request

{
  "account_id": {
    "external_id": "LAB-001"
  },
  "order_type": 1,
  "preassign_recipient_as_assignee": "yes",
  "payment_responsibility": "ordering_entity",
  "bundles": [
    {
      "sku": "BDL-STI-FULL",
      "quantity": 1,
      "flavor_id": 1,
      "diagnosis_code": "Z11.3",
      "billing_code": "87491",
      "submit_to_insurance": "No"
    },
    {
      "sku": "BDL-COVID-PCR",
      "quantity": 2,
      "flavor_id": 2
    }
  ],
  "ordering_entity_info": {
    "full_name": "Dr. Jane Smith",
    "ordered_by_entity_name": "ABC Laboratory",
    "ordered_by_id": "1234567890",
    "ordered_by_email": "drsmith@abclab.com",
    "ordered_by_phone": "+12125551234"
  },
  "ship_to": {
    "first_name": "John",
    "last_name": "Doe",
    "dob": "1990-05-15",
    "gender": "Male",
    "email": "johndoe@example.com",
    "phone": "+13105559876",
    "address": {
      "street_1": "123 Main Street",
      "street_2": "Apt 4B",
      "city": "Los Angeles",
      "region": "CA",
      "postal_code": "90001",
      "country": "US"
    }
  },
  "language_preference": "en",
  "custom_attributes": {
    "referral_source": "provider_portal",
    "internal_case_id": "CASE-2026-001"
  }
}

Full Sample: Create Order Response

{
  "statusCode": 200,
  "message": "Order created successfully!!!",
  "data": {
    "order": {
      "order_Id": 12345,
      "external_id": "T3JkZXItMTIzNDU="
    },
    "account_id": {
      "external_id": "LAB-001",
      "gdt_id": 42
    },
    "status": "Processing",
    "preassign_recipient_as_assignee": "yes",
    "order_type": "Shipment",
    "bundles": [
      {
        "sku": "BDL-STI-FULL",
        "quantity": 1,
        "flavor_id": 1,
        "diagnosis_code": "Z11.3",
        "billing_code": "87491",
        "submit_to_insurance": "No"
      },
      {
        "sku": "BDL-COVID-PCR",
        "quantity": 2,
        "flavor_id": 2
      }
    ],
    "ordering_entity_info": { "..." : "..." },
    "ship_to": { "..." : "..." },
    "custom_attributes": { "..." : "..." },
    "language_preference": "en",
    "customer_wallet": {
      "balance": 100,
      "product_units_added": 50,
      "service_units_added": 50
    },
    "selector_url": "",
    "user_selected_bundles": [],
    "warnings": []
  }
}

Order Creation: Important Behaviors

  • Approval Workflow: If enabled for your account, orders start in "Pending Approval" instead of "Processing".
  • Geographical Exclusions: Products may be excluded from certain regions. If ALL products in the order are excluded for the recipient’s region, the order is rejected.
  • Demographics Enforcement: If your account has enforce_validation enabled, gender, race, ethnicity, and gender_identity are validated against a pre-approved list.
  • Inventory Deduction: Product inventory is automatically decremented upon order creation.
  • User Auto-Creation: If preassign_recipient_as_assignee is "yes" (default), the recipient is automatically created as a user if they don’t already exist (matched by email).

6.2 Update Order

PUT /api/order

Updates an existing order (e.g., cancel, modify status).

Authentication: api-key header + account_id in body.


6.3 Assign Order

PUT /api/order/assign

Assigns an order to a specific user/patient.

Authentication: api-key header + account_id in body.


6.4 List Orders

GET /api/order?account_id=YOUR_ACCOUNT_ID

Retrieves all orders for your account.

Authentication: api-key header + account_id as query param.


6.5 Get Order by ID

GET /api/order/:order_id?account_id=YOUR_ACCOUNT_ID

Retrieves a specific order.

Authentication: api-key header + account_id as query param.


7. API Reference: Test Results

7.1 Retrieve Test Results

GET /api/test-results

Fetches lab results filtered by various criteria. Supports pagination.

Authentication

This endpoint supports two authentication modes:

  • GDT-level auth token – validated via token API (auth_token parameter)
  • Account-level API key – standard api-key header

Query Parameters

ParameterTypeRequiredMax LengthDescription
gdt_client_idstringYESGDT client identifier
account_idstringNo64 charsFilter by account
order_idstringNo40 charsFilter by specific order ID
patient_idstringNo64 charsFilter by patient user ID
phonestringNo20 charsFilter by patient phone
emailstringNo255 charsFilter by patient email (valid email format)
start_datestringNoDate range start (YYYY-MM-DD)
end_datestringNoDate range end (YYYY-MM-DD)
pagenumberNoPage number (default: 1)
limitnumberNoMax: 100Records per page (default: 10)
auth_tokenstringYESAuthentication token or API key

Query Rules

  • At least one filter is required: order_id, patient_id, phone/email, or date range (start_date + end_date)
  • If start_date is provided, end_date is required (and vice versa)
  • start_date must be <= end_date
  • Date range cannot exceed 90 days (returns a warning, no data)
  • When both phone and email are provided, they are combined with AND logic

Query Priority Order

If multiple filters are provided, they are processed in this priority:

  • order_id (highest priority)
  • patient_id
  • phone / email
  • start_date + end_date (lowest priority)

Pagination Limits

ConstraintLimit
Max records per page100
Max orders without pagination500
Max orders per patient100 (most recent)
Max patients for phone/email search50
Max orders for phone/email search200

Response Structure

{
  "status": "success",
  "statusCode": 200,
  "message": "Test results retrieved successfully",
  "data": [ ... ],
  "pagination": {
    "total": 42,
    "page": 1,
    "limit": 10,
    "totalPages": 5
  }
}

Result Data Object (per order)

{
  "account": {
    "external_id": "LAB-001",
    "gdt_id": "42"
  },
  "order": {
    "external_id": "EXT-ORD-001",
    "gdt_id": 12345
  },
  "patient_data": {
    "external_patient_id": "PAT-12345",
    "gdt_patient_id": "456",
    "patient_name": "John Doe",
    "email": "john.doe@example.com",
    "phone": "+12125551234",
    "address": {
      "address": "123 Main Street",
      "address_2": "Apt 4B",
      "city": "Los Angeles",
      "state_region": "CA",
      "postal_code": "90001",
      "country": "US"
    },
    "sex_at_birth": "Male",
    "gender_identity": "Man",
    "dob": "1990-05-15",
    "race": "White",
    "ethnicity": "Not Hispanic or Latino"
  },
  "results": [
    {
      "report": {
        "dates": {
          "collection_date": "2026-02-10",
          "result_date": "2026-02-15"
        },
        "lab_information": {
          "gdt_lab_id": "7",
          "lab_name": "Reference Lab Inc.",
          "lab_address": "456 Lab Drive, Chicago, IL"
        },
        "collection_site": {
          "gdt_location_id": "12",
          "address": {
            "address": "789 Collection Ave",
            "address_2": "",
            "city": "Los Angeles",
            "state_region": "CA",
            "postal_code": "90001",
            "country": "US"
          }
        },
        "pdf_base64": "JVBERi0xLjQK...",
        "report_comments": "All results within normal limits.",
        "test_comments": "",
        "test_type": "Molecular",
        "has_positive_or_abnormal_result": 0,
        "bundle_info": {
          "bundle_name": "STI Full Panel",
          "bundle_sku": "BDL-STI-FULL"
        },
        "data": [
          {
            "test": {
              "test_name": "Chlamydia trachomatis",
              "friendly_name": "Chlamydia Test",
              "product_description": "NAA test for CT detection",
              "test_id": 101,
              "lab_report_id": "RPT-2026-001",
              "value": "Negative",
              "product_sku": "PRD-CT-NAA",
              "product_category": "STI",
              "positive_or_abnormal_result": 0,
              "low_critical": "",
              "low_range": "",
              "high_range": "",
              "high_critical_range": "",
              "measurement_units": ""
            }
          },
          {
            "test": {
              "test_name": "Neisseria gonorrhoeae",
              "friendly_name": "Gonorrhea Test",
              "product_description": "NAA test for NG detection",
              "test_id": 102,
              "lab_report_id": "RPT-2026-001",
              "value": "Negative",
              "product_sku": "PRD-NG-NAA",
              "product_category": "STI",
              "positive_or_abnormal_result": 0,
              "low_critical": "",
              "low_range": "",
              "high_range": "",
              "high_critical_range": "",
              "measurement_units": ""
            }
          }
        ]
      }
    }
  ]
}

Test Result Item Fields Explained

FieldDescription
test_nameOfficial analyte/test name
friendly_namePatient-friendly test name
product_descriptionDescription of the test
test_idInternal test/product ID
lab_report_idLab’s report identifier
valueThe result value (e.g., "Negative", "12.5", "Detected")
product_skuProduct SKU identifier
product_categoryCategory grouping (e.g., "STI", "Wellness")
positive_or_abnormal_result0 = normal, 1 = positive/abnormal
low_criticalCritical low threshold (gender-sensitive: uses male or female value based on patient’s sex)
low_rangeNormal range lower bound
high_rangeNormal range upper bound
high_critical_rangeCritical high threshold (gender-sensitive)
measurement_unitsUnits of measurement (e.g., "mg/dL", "copies/mL")

Gender-sensitive ranges: The system uses the patient’s sex_at_birth to select the appropriate critical range. Male patients get low_critical_male / high_critical_male; female patients (default) get low_critical_female / high_critical_female. The normal range (low_range / high_range) is the same regardless of gender.

Data Visibility Control

Your account’s support entity settings control what result data you receive:

Setting: `permit_test_results_type`Behavior
1 or 3Full analyte-level data[] array included
Otherdata[] is returned as an empty array
Setting: `test_results_delivery_option`Behavior
1 or 2Lab PDF included
2 or 3GDT-generated PDF included

7.2 Individual Result Report

GET /api/individual_result_report

Retrieves an individual result report for a specific order/patient combination.

Supports the same dual authentication as test results.


8. API Reference: Groups

Groups organize users for bulk testing programs (corporate wellness, clinical studies, etc.).

8.1 Create Group

POST /api/group

FieldTypeRequiredValidationNotes
account_idobjectYESID object
group_namestringYESAlphanumeric, max 30
external_idstringNoMax 36, alphanumericYour system’s group ID
logo_urlstringNoValid URI, extensions: .jpg, .png, .gif, .webp, .heicGroup logo
ordering_modenumberNo1, 2, or 3
discount_modenumberNo1, 2, or 3
discount_percentagenumberConditional0-100 integer. Only when discount_mode = 2
promo_codestringConditionalAlphanumeric, 2-15 chars. Only when discount_mode = 2
all_bundlesnumberYES0 or 11 = all bundles available, 0 = specify individual bundles
bundlesarrayConditionalArray of { sku: string }. Required (min 1) when all_bundles = 0
attached_formsnumber[]YESArray of positive integers, min 1Form IDs to attach
communication_preferencesnumberYES0 or 1

Sample Request

{
  "account_id": { "external_id": "LAB-001" },
  "group_name": "Corporate Wellness Q1",
  "external_id": "GRP-CW-Q1",
  "all_bundles": 0,
  "bundles": [
    { "sku": "BDL-STI-FULL" },
    { "sku": "BDL-COVID-PCR" }
  ],
  "attached_forms": [1, 5],
  "communication_preferences": 1
}

8.2 Update Group

PUT /api/group

Same fields as create but all optional except account_id and group_id:

{
  "account_id": { "external_id": "LAB-001" },
  "group_id": { "gdt_id": 55 },
  "group_name": "Updated Group Name"
}

8.3 Delete Group

DELETE /api/group

{
  "account_id": { "external_id": "LAB-001" },
  "group_id": { "gdt_id": 55 }
}

Performs a soft delete.

8.4 Add Group Members

POST /api/group/member

FieldTypeRequiredValidation
account_idobjectYESID object
group_idobjectYESID object
membersarrayYESMin 1. Each: { external_id?, gdt_id?, email?, name? }

Each member must have at least one of external_id, gdt_id, or email.

If a member doesn’t exist, they are auto-created as a Patient (user_type: 1).

If a member is already in another group, they are moved to this group.

{
  "account_id": { "external_id": "LAB-001" },
  "group_id": { "gdt_id": 55 },
  "members": [
    { "email": "alice@example.com", "name": "Alice Smith" },
    { "gdt_id": 789 },
    { "external_id": "PAT-999" }
  ]
}

8.5 Remove Group Members

DELETE /api/group/member

Same structure as add members (without name):

{
  "account_id": { "external_id": "LAB-001" },
  "group_id": { "gdt_id": 55 },
  "members": [
    { "email": "alice@example.com" },
    { "gdt_id": 789 }
  ]
}

9. Webhooks (Inbound to Diagnostic.ly)

These are endpoints where external systems push data INTO Diagnostic.ly.

9.1 Crelio Lab Webhooks

If your LIMS is Crelio, these are the webhook endpoints that receive lab events:

EndpointEventDescription
POST /api/crelio/sample_receivedSample ReceivedSpecimen has arrived at the lab
POST /api/crelio/sample_dismissedSample DismissedSpecimen was rejected by the lab
POST /api/crelio/test_dismissedTest DismissedA specific test was cancelled
POST /api/crelio/report_submitReport SubmittedStructured test results submitted
POST /api/crelio/report_submit_pdfPDF ReportPDF result report uploaded
POST /api/crelio/consolidated_report_submit_pdfConsolidated PDFMulti-test consolidated PDF report
POST /api/crelio/consolidated_final_report_submitFinal ReportFinal consolidated structured report

9.2 Generic Test Results Webhook

POST /api/test-results

For labs that aren’t using Crelio, this is the generic endpoint for submitting test results.

9.3 NetSoft/Precision Results Webhook

POST /api/netsoft/results

For labs submitting test results. This is the most relevant endpoint for a lab sending results back into Diagnostic.ly.

Complete Request Schema

{
  "gdt_client_id": "string (REQUIRED)",
  "account_id": {
    "external_id": "string (optional)",
    "gdt_id": "string (REQUIRED)"
  },
  "order_id": {
    "external_order_id": "string (optional)",
    "gdt_order_id": "string (REQUIRED)"
  },
  "patient_info": {
    "external_patient_id": "string (optional)",
    "gdt_id": "string (REQUIRED)",
    "patient_name": "string (optional)",
    "patient_address": "string (optional)",
    "patient_address2": "string (optional)",
    "patient_city": "string (optional)",
    "patient_region": "string (optional)",
    "patient_postalcode": "string (optional)",
    "patient_country": "string (optional)",
    "patient_phone": "string (optional)",
    "patient_email": "string (optional, valid email)"
  },
  "ordering_info": {
    "filler_order_id": "string (optional)",
    "universal_service_identifier": "string (optional)",
    "ordering_provider": "string (optional)",
    "placer_field_1": "string (optional)",
    "specimen_priority": "string (optional)",
    "result_status": "string (optional)"
  },
  "results": {
    "report": [
      {
        "report_id": "string (REQUIRED)",
        "dates": {
          "result_date": "ISO date string (REQUIRED)"
        },
        "has_positive_or_abnormal_result": 0,
        "has_inconclusive_result": 0,
        "has_insufficient_quantity_result": 0,
        "has_missing_specimen": 0,
        "pdf": {
          "pdf_url": "https://yourlab.com/reports/12345.pdf",
          "pdf_base64": "JVBERi0xLjQK..."
        },
        "products": [
          {
            "product": {
              "external_product_sku": "SKU-001",
              "result_type": "Qualitative",
              "observation_identifier": "CT-NAA",
              "result_value": "Negative",
              "interpretation": "No CT DNA detected",
              "legal": "For clinical use only"
            }
          }
        ]
      }
    ]
  }
}

Report-Level Flag Fields

FieldTypeValuesDescription
has_positive_or_abnormal_resultnumber0 or 1Any positive/abnormal result in this report
has_inconclusive_resultnumber0 or 1Any inconclusive result
has_insufficient_quantity_resultnumber0 or 1Insufficient specimen quantity
has_missing_specimennumber0 or 1Specimen not received/missing

PDF Submission

You can provide the PDF report in either format (or both):

FieldDescription
pdf_urlURL to the PDF (Diagnostic.ly will download and store in S3)
pdf_base64Base64-encoded PDF content (Diagnostic.ly will decode and store in S3)

What Happens Server-Side

  • Validates account, patient, and order associations
  • Checks that the order exists and is not already completed
  • For each report: downloads/decodes PDF, uploads to S3
  • Creates test_results_reports and test_results_reports_items records
  • Logs to webhook_logs for audit trail
  • On failure: sends alert email to engineering

Success Response

{
  "success": true,
  "message": "Results processed successfully"
}

Error Response

{
  "success": false,
  "message": "Descriptive error message"
}

9.4 PilotFish HL7 Middleware Webhook

POST /api/pilotfish

For HL7-based integrations via PilotFish middleware.

9.5 Order Status Update Webhook

POST /api/order/status_update

External systems can push order status changes into Diagnostic.ly.

9.6 Specimen/Shipment Data Webhook

POST /api/ (root webhook endpoint)

Generic inbound webhook for specimen and shipment data from support entities. Authentication is via api_key header matching or hostname-based matching.


10. Webhooks (Outbound from Diagnostic.ly)

Diagnostic.ly pushes status updates to your system via outbound webhooks.

Configuration

Your webhook URL and bearer token are configured in the support_entity_credentials table:

  • outbound_webhook_url – Your endpoint URL
  • outbound_webhook_key – Bearer token included in the Authorization header

How It Works

  • A cron job runs periodically (every 12 hours) to find orders with status changes
  • Only sends a webhook if the order status has changed since the last webhook
  • Retries are logged; failures are tracked in webhook_logs

Outbound Webhook Payload

{
  "order_id": 12345,
  "external_account_id": "LAB-001",
  "account_id": {
    "gdt_external_id": "LAB-001",
    "gdt_id": 42
  },
  "order_status": "Arrived at Lab",
  "order_type": "Shipment",
  "recipient_info": {
    "name": "John Doe",
    "email": "johndoe@example.com",
    "phone": "+13105559876",
    "address": {
      "address": "123 Main Street",
      "address2": "Apt 4B",
      "city": "Los Angeles",
      "state": "CA",
      "postal": "90001",
      "country": "US"
    }
  },
  "preassign_recipient_flag": 1,
  "outbound_shipment": {
    "carrier_code": "usps",
    "service_level": "priority",
    "tracking_id": "9400111899223100001234",
    "shipment_date": "2026-02-10",
    "arrival_date": "2026-02-13",
    "tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400111899223100001234"
  },
  "inbound_shipment": {
    "carrier_code": "usps",
    "service_level": "priority",
    "tracking_id": "9400111899223100005678",
    "shipment_date": "2026-02-14",
    "arrival_date": "",
    "tracking_url": ""
  },
  "bundles": [
    {
      "internal_sku": "BDL-STI-FULL",
      "external_sku": "VENDOR-STI-001",
      "bundle_name": "STI Full Panel",
      "collection_date": null,
      "kit_id": "KIT-ABC-123",
      "products": [
        {
          "product_name": "Chlamydia/Gonorrhea Test",
          "internal_sku": "PRD-CT-NG",
          "external_sku": "VENDOR-PRD-001",
          "kit_id": "KIT-ABC-123",
          "collection_date": null
        }
      ]
    }
  ]
}

10.2 Outbound Test Results Webhook

In addition to order status webhooks, Diagnostic.ly also pushes test results to your webhook endpoint when new results are available.

Trigger

  • A cron job checks for results where enable_webhook_notification = 1
  • Results are only sent once (marked as sent after successful delivery)

Test Results Webhook Payload

{
  "gdt_client_id": "string",
  "gdt_client_name": "string",
  "account": {
    "external_id": "LAB-001",
    "gdt_id": "42"
  },
  "order": {
    "external_id": "EXT-ORD-001",
    "gdt_id": "12345"
  },
  "patient_data": {
    "patient_id": "456",
    "external_patient_id": "PAT-12345",
    "gdt_patient_id": "456",
    "patient_name": "John Doe",
    "email": "john.doe@example.com",
    "phone": "+12125551234",
    "address": {
      "address": "123 Main Street",
      "address_2": "Apt 4B",
      "city": "Los Angeles",
      "state_region": "CA",
      "postal_code": "90001",
      "country": "US"
    },
    "sex_at_birth": "Male",
    "gender_identity": "Man",
    "dob": "1990-05-15",
    "race": "White",
    "ethnicity": "Not Hispanic or Latino"
  },
  "results": [
    {
      "report": {
        "dates": {
          "collection_date": "2026-02-10",
          "result_date": "2026-02-15"
        },
        "lab_information": {
          "gdt_lab_id": "7",
          "lab_name": "Reference Lab",
          "lab_address": "456 Lab Drive"
        },
        "collection_site": {
          "gdt_location_id": "12",
          "address": { "..." }
        },
        "pdf_url": "https://s3.amazonaws.com/.../lab_report.pdf",
        "pdf_base64": "JVBERi0xLjQK...",
        "gdt_pdf_url": "https://s3.amazonaws.com/.../gdt_report.pdf",
        "report_comments": "All within normal limits",
        "test_comments": "",
        "test_type": "Molecular",
        "has_positive_or_abnormal_result": 0,
        "bundle_info": {
          "bundle_name": "STI Full Panel",
          "bundle_sku": "BDL-STI-FULL"
        },
        "data": [
          {
            "test": {
              "test_name": "Chlamydia trachomatis",
              "friendly_test_name": "Chlamydia",
              "product_description": "NAA test",
              "product_sku": "PRD-CT-NAA",
              "product_category": "STI",
              "test_id": 101,
              "lab_report_id": "RPT-001",
              "value": "Negative",
              "positive_or_abnormal_result": 0,
              "low_critical": "",
              "low_range": "",
              "high_range": "",
              "high_critical_range": "",
              "measurement_units": ""
            }
          }
        ]
      }
    }
  ]
}

Response Codes from the Cron

StatusMeaning
200All results sent successfully (or none to send)
207Partial success – some results sent, some failed
500All sends failed

On any failure, an alert email is sent to the engineering team.


How Your System Should Handle Outbound Webhooks

  • Endpoint: Expose an HTTPS POST endpoint at your configured outbound_webhook_url
  • Authentication: Validate the Authorization: Bearer header
  • Response: Return HTTP 200 to acknowledge receipt
  • Idempotency: Handle duplicate deliveries gracefully (same order_id + status)

Webhook Request Headers

POST /your-webhook-endpoint HTTP/1.1
Authorization: Bearer wh_key_xyz789...
Content-Type: application/json

11. Address Verification

11.1 Verify Address

POST /api/address-verify

Note: This endpoint does NOT require API key authentication.

Uses PostGrid for address verification.

Request Body

FieldTypeRequiredValidation
request_idstringYESYour tracking ID
recipient_namestringYES
address_1stringYES
address_2stringNoAllows empty/null
citystringYES
regionstringYESState/province
urbanizationstringNoAllows empty/null
postal_codestringNoAllows empty/null
country_codestringYESExactly 2 chars (ISO-2)

Sample Request

{
  "request_id": "REQ-001",
  "recipient_name": "John Doe",
  "address_1": "123 Main St",
  "address_2": "Apt 4B",
  "city": "New York",
  "region": "NY",
  "postal_code": "10001",
  "country_code": "US"
}

Response – Address Verified

{
  "message": "Address verified successfully.",
  "verifiedAddress": { ... }
}

Response – Address Not Verified (Suggestions Returned)

{
  "message": "Address could not be verified. Suggested alternatives are provided.",
  "alternatives": [ ... ]
}

11.2 Address Suggestions

POST /api/address-verify/suggest

{
  "address": {
    "line1": "123 Main St",
    "line2": "",
    "city": "New York",
    "postalOrZip": "10001",
    "provinceOrState": "NY",
    "country": "US"
  }
}

12. Order Lifecycle & Status Reference

Order Status Flow

                    +------------------+
                    | Pending Approval |  (if approval workflow enabled)
                    +--------+---------+
                             |
                    +--------v---------+
           +------->   Processing     <-------+
           |        +--------+---------+      |
           |                 |                |
           |        +--------v---------+      |
           |        |    In Transit    |      |
           |        +--------+---------+      |
           |                 |                |
           |        +--------v---------+      |
           |        |     Arrived      |      |  Reshipment
           |        +--------+---------+      |  Processing
           |                 |                |     -> Reshipment
           |        +--------v---------+      |        Arrived
           |        | Awaiting Return  |      |
           |        |    Shipment      |      |
           |        +--------+---------+      |
           |                 |                |
           |        +--------v---------+      |
           |        | In Transit to Lab|------+
           |        +--------+---------+
           |                 |
           |        +--------v---------+
           |        |  Arrived at Lab  |
           |        +--------+---------+
           |                 |
           |        +--------v---------+
           |        |Completed Shipped |
           |        +------------------+
           |
           |   (Exception paths)
           |
           +--- Delayed
           +--- Delayed Beyond Max Days
           +--- Exception
           +--- Canceled
           +--- Returned
           +--- Refunded

Complete Order Status Enum

StatusDescription
ProcessingOrder created, awaiting fulfillment
Pending ApprovalAwaiting provider approval (if approval workflow enabled)
ApprovedOrder approved by provider
declinedOrder denied by provider
SnoozedOrder approval deferred
Pending ShipmentQueued for shipment
In TransitKit shipped, en route to patient
ArrivedKit delivered to patient
Awaiting Return ShipmentWaiting for patient to return specimen
In Transit to LabSpecimen shipped, en route to lab
Arrived at LabSpecimen received at lab
Completed ShippedResults complete, order fulfilled
DelayedShipment delayed
Delayed Beyond Max DaysShipment delayed past maximum SLA
ExceptionOrder has an exception/issue
CanceledOrder cancelled
ReturnedKit returned to sender
RefundedOrder refunded
Reshipment ProcessingReplacement kit being prepared
Reshipment ArrivedReplacement kit delivered

Order Bundle Status Values

When approval workflows are enabled, individual bundles within an order can have these statuses:

StatusDescription
approvedBundle approved by provider
deniedBundle denied by provider
not_submittedBundle not yet submitted for approval
snoozedBundle approval deferred

13. Enum & Constant Reference

order_type

ValueNameDescription
1ShipmentKit shipped to patient
2Hand to PatientKit given directly
3RegistrationNo physical kit

flavor_id

ValueName
1TEST_ONLY
2TEST_AND_EDUCATION
3EDUCATION_ONLY

user_type

ValueRole
1Patient
2Account Admin
3Observer
5Manager
7Provider
8Medical Staff
10Support Entity Manager

payment_responsibility

ValueDescription
"ordering_entity"Lab pays (default)
"patient"Patient pays
"shared"Split between lab and patient
nullNo payment info

gender (Order ship_to)

Value
"Male"
"Female"
"Others"

Case-insensitive for orders. For users, only "Male" and "Female" are accepted.

preassign_recipient_as_assignee

ValueDescription
"yes"Auto-assign the recipient as the test taker (default)
"no"Do not auto-assign

shipment_handled_externally

ValueDescription
"Y" / "Yes" / "yes"You handle shipping (must provide carrier_id + tracking_id)
"N" / "No" / "no"Diagnostic.ly handles shipping

submit_to_insurance

Value
"Yes"
"No"

timezone_support

ValueDescription
1Timezone support enabled
2Timezone support disabled

communication_preferences

ValueDescription
0Opted out
1Opted in

Shipment Types

IDNameDescription
1CUSTOMEROutbound shipment (to patient)
2RETURNInbound shipment (specimen to lab)

Regex Patterns

PatternRegexUse
Phone (with country code)^\+\d{10,15}$All phone fields in orders
Phone (alternate)^\+\d{1,3}\d{8,10}$Some phone fields in users/fax
Alphanumeric^[a-zA-Z0-9\s]+$external_id, names, etc.
Date filter^\d{4}-\d{2}-\d{2}$Date query parameters
Date-time filter`^\d{4}-(0[1-9]\1[0-2])-(0[1-9]\[12]\d\3[01]) (0\d\1\d\2[0-3]):([0-5]\d):([0-5]\d)$`DateTime query parameters

14. Error Handling

Error Response Format

All errors follow this structure:

{
  "type": "ClientError",
  "statusCode": 400,
  "message": "Descriptive error message"
}

Error Types

TypeDefault StatusDescription
ClientError400Bad request, validation failure, auth failure
ServerError500Internal server error
ShipheroError400ShipHero fulfillment integration error
ShipengineError400ShipEngine shipping integration error
InterfaxError400InterFax faxing integration error

Common Error Scenarios

ScenarioStatusMessage
Missing required field400"The field {field_name} is required"
Empty required field400"The field {field_name} cannot be empty"
Invalid field type400"Invalid characters ({value}) passed into {field_name}..."
Field too long400"The field length for {field_name} is {limit} but your data was {length} characters..."
Invalid email400"Invalid characters ({value}) passed into {field_name}..."
Missing API key400"API key is required"
Invalid API key400"Invalid API key"
Missing account ID400"Account id is required"
Account not found400"Account (X) not found or not linked with any support entity."
Inactive account400"Account (X) is not active."
API method not permitted400"Invalid api method"
Bundle not found400"Bundle with SKU {sku} not found"
Bundle not active400"Bundle {sku} is not active"
Geographical exclusion400Bundle/product excluded for recipient’s region
Duplicate user400User with same email already exists in account
Invalid JSON body400"Invalid data passed in request body. Valid data format: JSON"
Internal error500"Internal server error"

Validation Errors

Joi validation returns only the first error encountered. The message is extracted from the first error detail:

{
  "type": "ClientError",
  "statusCode": 400,
  "message": "The field ship_to.email is required"
}

15. Integration Workflow: End-to-End Example

Here is the complete workflow for a lab integrating with Diagnostic.ly:

Step 1: Register a Patient

POST /api/user HTTP/1.1
Host: api.diagnostic.ly
Content-Type: application/json
api-key: sk_live_abc123

{
  "account_id": { "external_id": "LAB-001" },
  "external_id": "PAT-12345",
  "name": "John Doe",
  "email": "john.doe@example.com",
  "active": true,
  "country_code": "US",
  "phone_number": "+12125551234",
  "timezone_support": 1,
  "user_type": 1,
  "dob": "1990-05-15",
  "gender": "Male"
}

Response: { "data": { "gdt_id": 456 } }

Step 2: Verify Shipping Address (Optional)

POST /api/address-verify HTTP/1.1
Host: api.diagnostic.ly
Content-Type: application/json

{
  "request_id": "ADDR-001",
  "recipient_name": "John Doe",
  "address_1": "123 Main St",
  "city": "Los Angeles",
  "region": "CA",
  "postal_code": "90001",
  "country_code": "US"
}

Step 3: Place an Order

POST /api/order HTTP/1.1
Host: api.diagnostic.ly
Content-Type: application/json
api-key: sk_live_abc123

{
  "account_id": { "external_id": "LAB-001" },
  "order_type": 1,
  "bundles": [
    {
      "sku": "BDL-STI-FULL",
      "quantity": 1,
      "flavor_id": 1
    }
  ],
  "ordering_entity_info": {
    "full_name": "Dr. Jane Smith",
    "ordered_by_id": "1234567890",
    "ordered_by_email": "drsmith@lab.com"
  },
  "ship_to": {
    "first_name": "John",
    "last_name": "Doe",
    "email": "john.doe@example.com",
    "phone": "+12125551234",
    "dob": "1990-05-15",
    "gender": "Male",
    "address": {
      "street_1": "123 Main St",
      "city": "Los Angeles",
      "region": "CA",
      "postal_code": "90001",
      "country": "US"
    }
  }
}

Response: { "data": { "order": { "order_Id": 12345 }, "status": "Processing" } }

Step 4: Track Order Status

Option A: Poll the API

GET /api/order/12345?account_id=LAB-001 HTTP/1.1
Host: api.diagnostic.ly
api-key: sk_live_abc123

Option B: Receive Outbound Webhooks (recommended)

Your endpoint receives status updates automatically:

// Your server receives:
{
  "order_id": 12345,
  "order_status": "In Transit",
  "outbound_shipment": {
    "tracking_id": "9400111899223100001234",
    "tracking_url": "https://tools.usps.com/..."
  },
  ...
}

Step 5: Submit Test Results (If You Are the Processing Lab)

If your lab processes the specimens and needs to push results back:

Via NetSoft Webhook:

POST /api/netsoft/results HTTP/1.1
Host: api.diagnostic.ly
Content-Type: application/json

{
  "order_info": {
    "order_id": { "gdt_order_id": 12345 },
    "account_id": { "gdt_account_id": 42 }
  },
  "patient_info": {
    "first_name": "John",
    "last_name": "Doe",
    "dob": "1990-05-15"
  },
  "ordering_entity_info": {
    "provider_name": "Dr. Jane Smith",
    "provider_npi": "1234567890"
  },
  "report": {
    "results": [
      {
        "test_name": "Chlamydia trachomatis",
        "result_value": "Negative",
        "reference_range": "Negative",
        "units": "",
        "flag": "Normal"
      }
    ],
    "pdf": {
      "pdf_url": "https://yourlab.com/reports/12345.pdf"
    }
  }
}

Step 6: Retrieve Test Results

GET /api/test-results?account_id=LAB-001&order_id=12345&auth_token=sk_live_abc123 HTTP/1.1
Host: api.diagnostic.ly

Appendix: Quick Reference Card

Essential Headers

api-key: YOUR_API_KEY
Content-Type: application/json

Core Endpoints

ActionMethodEndpoint
Create UserPOST/api/user
Update UserPUT/api/user/:id
List UsersGET/api/user
Delete UserDELETE/api/user
Create OrderPOST/api/order
Update OrderPUT/api/order
List OrdersGET/api/order
Get OrderGET/api/order/:order_id
Get ResultsGET/api/test-results
Verify AddressPOST/api/address-verify
Create GroupPOST/api/group
Add MembersPOST/api/group/member

Swagger Documentation

  • External (Partner-Facing): https://api.diagnostic.ly/docs
  • Internal: https://api.diagnostic.ly/internal/docs

Health Check

GET /health HTTP/1.1
Host: api.diagnostic.ly

This document was generated from the diagnostic.ly-nodejs source code. For the latest updates, refer to the Swagger documentation at /docs.