This How-To applies to you if:
- You already have patient information in your own systems
- You already have access to or collect your own test result data
- You wish to submit this data for analysis by Thorne
# Prerequisites
This How-To assumes that you have already taken care of understand the following:
- How to authenticate to the Thorne API
- How to construct Rest API calls or how to use one of our client SDK libraries
# Synopsis
In order to "bring your own data" to Thorne for analysis the following things need to occur:
# Register your patient
- A valid patient record must be registered with Thorne utilizing our API
- For the best and most accurate analysis, we always suggest making sure to provide as much information about the patients medications, health history, and lifestyle habits that you can
# Upload your test results
- Test results must be uploaded and attached to the patient record utilizing our API (we refer to this collection of test results as a "batch")
- You may optionally request that this batch of results be associated with an analysis package according offered by Thorne
- The analysis package chosen dictates what kind of computational analysis is performed as well as what is included in the final output
- Finally the uploaded results batch will be validated against Thorne's database before analysis can proceed, which includes:
- Matching the measurement to a known analyte, biomarker, or other measure (generally referred to by Thorne as a "marker") in Thorne's system
- Verifying that the value and units of measure for the test result are valid for the marker
# Retrieve final analysis
- Assuming validation was successful the analysis is performed and, once successfully finished, a webhook notification can be sent to your system alerting you that the analysis is complete and the final results can be retrieved through the API
# An example: Biological Age
# Required patient information
The specifics of required patient information is documented in the interactive API documentation, but generally include:
- A patient identifier that means something to your system (e.g. a medical record number, patient identifier, etc)
- First and Last name
- Date of Birth
- Gender (sex assigned at birth)
# Required test results
Biological Age is a complex measure looking at both the health of the patient as a whole as well as system-specific health (e.g. Metabolic Health, Liver Health, etc.). As such a wide range of measures are needed to perform this analysis with any meaningful accuracy. While not all of the below are required, the analysis will not be able to be run if too many of the needed results are unavailable.
| Thorne API Identifier | Marker Name |
|---|---|
| TCHOL | Total Cholesterol |
| HDL-C | HDL Cholesterol |
| LDL-Ct | LDL Cholesterol |
| TRIG | Triglycerides |
| GLUC | Glucose |
| %A1C | Hemoglobin A1c |
| DHEA | DHEA |
| ALB | Albumin |
| BUN | Blood Urea Nitrogen (BUN) |
| CK | Creatinine |
| CA | Calcium |
| CL | Chloride |
| CO2 | Carbon Dioxide (Bicarbonate) |
| K | Potassium |
| NA | Sodium |
| GLO | Globulin |
| ALP | Alkaline Phosphatase (ALP) |
| ALT | (ALT) |
| AST | (AST/SGOT) |
| TBIL | Total Bilirubin |
| TP | Total Protein |
| WBC | White Blood Cells (count) |
| RBC | Red Blood Cells (count) |
| LY# | Lymphocytes count |
| BA# | Basophils count |
| EO# | Eosinophils |
| HCT | Hematocrit |
| HGB | Hemoglobin |
| MCH | Mean Corpuscular Hemoglobin (MCH) |
| MCHC | Mean Corpuscular Hemoglobin Concentration (MCHC) |
| MCV | Mean Corpuscular Volume (MCV) |
| MO# | Monoctyes (count) |
| NE# | Neutrophils |
| PLT | Platelets (count) |
| RDW | Red Blood Cell Distribution Width (RDW) |
A note on units - Thorne can generally convert units as needed
# Desirable additional patient data
To help create the best possible results some additional patient demographic and lifestyle data should be provided if available:
| Thorne API Identifier | Meaning | Possible Values |
|---|---|---|
| BMI | Body-Mass Index | Positive decimal value (e.g. 22.4) |
| RACE | Race | WHITE, AFRICAN_AMERICAN, ASIAN, PACIFIC_ISLANDER, NATIVE_AMERICAN, UNKNOWN |
| ETHNICITY | Ethnicity | LATINO, NON_LATINO, UNKNOWN |
| STATINS | Patient currently uses statins | TRUE, FALSE |
| SMOKER | Patient currently smokes | TRUE, FALSE |
| PREGNANCY_STATUS | Pregnancy status or plans | PREGNANT, TRYING_TO_GET_PREGNANT, LACTATING, USING_CONTRACEPTIVES |
| MENSTRUAL_STATUS | Menstrual / menopause status | REGULAR_PERIOD, IRREGULAR_PERIOD, NO_PERIOD, PERIMENOPAUSE, MENOPAUSE, POSTMENOPAUSE |
| SLEEP_QUALITY_SEVERITY | How does patient rate quality of sleep | 1, 2, 3, 4, 5 (1 = horrible, 5 = excellent) |
| AGING_PRIMARY_HEALTH_CONCERN | Patient's main health concern for the future | CELLULAR_HEALTH, ORGAN_HEALTH, CHRONIC_DISEASE, VITALITY |
| EXERCISE_MINUTES_PER_WEEK | Typical minutes of exercise in an average week | LESS_THAN_30_MIN, 30_TO_90_MIN, 90_TO_150_MIN, AT_LEAST_150_MIN |
| HOURS_SITTING_PER_DAY | Hours spent sitting in a typical day | LESS_THAN_8_HOURS, 8_TO_10_HOURS, 10_TO_12_HOURS, 12_TO_14_HOURS, MORE_THAN_14_HOURS |
| WEEKLY_CALMING_ACTIVITY | How many days in a usual week does patient do calming / relaxing activities | 1: ("Rare / Never"), 2: ("< 1 per week"), 3: ("1 per week"), 4: ("2-4 per week"), 5: ("> 4 per week") |
| DAILY_ALCOHOL | How many alcoholic drinks in a typical day | 1: ("Rare / Never"), 2: ("< 1 serving"), 3: ("1 serving"), 4: ("2-3 servings"), 5: ("> 3 servings") |
| DAILY_PROCESSED_FOODS | How many servings of packaged / processed foods in a typical day | 1: ("Rare / Never"), 2: ("< 1 serving"), 3: ("1 serving"), 4: ("2-3 servings"), 5: ("> 3 servings") |
| DAILY_SWEETS | How many servings of sweets in a typical day | 1: ("Rare / Never"), 2: ("< 1 serving"), 3: ("1 serving"), 4: ("2-3 servings"), 5: ("> 3 servings") |
# Step by step examples with the API
# Step 1: Registering the Patient Record
In this example we'll assume that the source system has demographic information, current medications, and some amount of health history information. The integrator needs to translate this information from their source systems into the representations understood by the Thorne API and post to the patient endpoint. The required information is provided and we include the optional information we have available in the "healthProfile" section.
Here is an example request to the API to create a patient record:
POST /api/v1/patients
{
"patientIdentifier": "MRN84327",
"firstName": "John",
"lastName": "Doe",
"gender": "MALE",
"dateOfBirth": "1973-12-05",
"healthProfile": {
"RACE": "WHITE",
"ETHNICITY": "NON_LATINO",
"BMI": 27.4,
"SMOKER": "FALSE",
"DAILY_ALCOHOL": 2,
"EXERCISE_MINUTES_PER_WEEK": "90_TO_150_MIN"
}
}
# Step 2: Uploading Test Results Batch
Now that the patient record exists, we upload the test results batch and attach to the patient record. Since we want Biological Age out of the system, we associate this batch with the "BIOAGE" package identifier. We don't have the full set of preferred test results as described above, but there are enough for the analysis to be completed.
The integrator needs to translate the test result information encoded in the source system into the format required to upload to the Thorne API. Typically this just involves mapping analyte names or codes to the Thorne API identifier from the table above.
Here is an example request to the API to upload test results for the patient we just created. The specifics of each element of this request are documented in the interactive API documentation.
In this example we see:
- A unique identifier for this batch of results
- The timestamp at which the measurements were reported
- Specifics of the analysis package to be run for this patient, namely BIOAGE
- A list of test results containing the following required fields:
- Thorne's API identifier for the measurement, as listed in the table above
- A name of the measurement, this can be anything relevant to your system it just must be present
- The specimen type from which the measurement was taken .. for Biological Age this must be VENIPUNCTURE
- The value observed for the measurement
- The units the value is reported in
- The date on which the blood sample was taken
POST /api/v1/patients/MRN84327/
{
"batchIdentifier": "MRN84327_2022_03_19_batch1",
"reportedTimestamp": "2022-03-19T09:38:27Z",
"patientOrder": {
"orderIdentifier": "MRN84327_2022_03_19",
"packageIdentifier": "BIOAGE"
},
"testResults": [
{
"identifier": "%A1C",
"name": "HbA1c",
"specimen": "VENIPUNCTURE",
"value": "4.4",
"units": "%",
"collectionDate": "2022-03-17"
},
{
"identifier": "ALB",
"name": "Albumin",
"specimen": "VENIPUNCTURE",
"value": "4.7",
"units": "g/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "ALT",
"name": "ALT",
"specimen": "VENIPUNCTURE",
"value": "37",
"units": "IU/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "AST",
"name": "AST",
"specimen": "VENIPUNCTURE",
"value": "20",
"units": "IU/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "BA#",
"name": "Basophils",
"specimen": "VENIPUNCTURE",
"value": "10E7",
"units": "Cells/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "BUN",
"name": "BUN",
"specimen": "VENIPUNCTURE",
"value": "41",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "CA",
"name": "Calcium",
"specimen": "VENIPUNCTURE",
"value": "10.1",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "CK",
"name": "Creatinine",
"specimen": "VENIPUNCTURE",
"value": "0.89",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "CL",
"name": "Chloride",
"specimen": "VENIPUNCTURE",
"value": "109",
"units": "mmol/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "DHEA",
"name": "DHEA-S",
"specimen": "VENIPUNCTURE",
"value": "196",
"units": "μg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "EO#",
"name": "Eosinophils",
"specimen": "VENIPUNCTURE",
"value": "1.3E8",
"units": "Cells/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "GLO",
"name": "Globulin",
"specimen": "VENIPUNCTURE",
"value": "1",
"units": "g/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "GLUC",
"name": "Glucose ",
"specimen": "VENIPUNCTURE",
"value": "99",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "HDL-C",
"name": "HDL",
"specimen": "VENIPUNCTURE",
"value": "52",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "HGB",
"name": "Hemoglobin",
"specimen": "VENIPUNCTURE",
"value": "16.5",
"units": "g/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "HCT",
"name": "Hematocrit",
"specimen": "VENIPUNCTURE",
"value": "46",
"units": "%",
"collectionDate": "2022-03-17"
},
{
"identifier": "K",
"name": "Potassium",
"specimen": "VENIPUNCTURE",
"value": "4.4",
"units": "mmol/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "LDL-Ct",
"name": "LDL",
"specimen": "VENIPUNCTURE",
"value": "120",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "LY#",
"name": "Lymphocytes",
"specimen": "VENIPUNCTURE",
"value": "1.95E9",
"units": "Cells/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "MCH",
"name": "MCH",
"specimen": "VENIPUNCTURE",
"value": "31",
"units": "pg",
"collectionDate": "2022-03-17"
},
{
"identifier": "MCHC",
"name": "MCHC",
"specimen": "VENIPUNCTURE",
"value": "36",
"units": "g/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "MCV",
"name": "MCV",
"specimen": "VENIPUNCTURE",
"value": "85",
"units": "fL",
"collectionDate": "2022-03-17"
},
{
"identifier": "MO#",
"name": "Monocytes",
"specimen": "VENIPUNCTURE",
"value": "4.7E8",
"units": "Cells/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "MPV",
"name": "MPV",
"specimen": "VENIPUNCTURE",
"value": "11.2",
"units": "fL",
"collectionDate": "2022-03-17"
},
{
"identifier": "NA",
"name": "Sodium",
"specimen": "VENIPUNCTURE",
"value": "147",
"units": "mmol/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "NE#",
"name": "Neutrophils ",
"specimen": "VENIPUNCTURE",
"value": "3.04E9",
"units": "Cells/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "PLT",
"name": "Platelets",
"specimen": "VENIPUNCTURE",
"value": "1.72E11",
"units": "Cells/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "RBC",
"name": "Erythrocytes (RBC count)",
"specimen": "VENIPUNCTURE",
"value": "5.39E12",
"units": "Cells/L",
"collectionDate": "2022-03-17"
},
{
"identifier": "RDW",
"name": "RDW",
"specimen": "VENIPUNCTURE",
"value": "12.8",
"units": "%",
"collectionDate": "2022-03-17"
},
{
"identifier": "TBIL",
"name": "Bilirubin ",
"specimen": "VENIPUNCTURE",
"value": "1.33",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "TCHOL",
"name": "Total Cholesterol",
"specimen": "VENIPUNCTURE",
"value": "215",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "TP",
"name": "Total Protein",
"specimen": "VENIPUNCTURE",
"value": "7.3",
"units": "g/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "TRIG",
"name": "triglycerides",
"specimen": "VENIPUNCTURE",
"value": "213",
"units": "mg/dL",
"collectionDate": "2022-03-17"
},
{
"identifier": "WBC",
"name": "Leukocytes (WBCs)",
"specimen": "VENIPUNCTURE",
"value": "5.6E9",
"units": "Cells/L",
"collectionDate": "2022-03-17"
}
]
}
# Successful result upload
When posting this request to the server you receive an immediate response indicating whether or not the import of the test results was successful.
Here's an example of a successful response, which shows that the results were imported and released for use in downstream processing and analysis:
{
"id": 4,
"batchIdentifier": "MRN84327_2022_03_19_batch1",
"reportedTimestamp": "2022-03-19T09:38:27Z",
"createdTimestamp": "2022-08-04T22:34:25.114Z",
"releasedTimestamp": "2022-08-04T22:34:25.11Z",
"resultCount": 34,
"status": "RELEASED"
}
# Errored result upload
The Thorne API does the best it can to handle as many of your uploaded results as possible and will report what went wrong for those it cannot handle.
Here's an example of an unsuccessful response:
{
"id": 5,
"batchIdentifier": "MRN84327_2022_03_19_batch3",
"reportedTimestamp": "2022-03-19T09:38:27Z",
"createdTimestamp": "2022-08-04T22:41:44.351Z",
"resultCount": 33,
"status": "ERRORED"
}
# Responding to upload errors
To retrieve detailed information about what went wrong:
GET /api/v1/patients/MRN84327/results-upload-batches/MRN84327_2022_03_19_batch_with_errors
{
"id": 5,
"batchIdentifier": "MRN84327_2022_03_19_batch_with_errors",
"reportedTimestamp": "2022-03-19T09:38:27Z",
"createdTimestamp": "2022-08-04T22:41:44Z",
"resultCount": 33,
"status": "ERRORED",
"errors": [
"Invalid units (pg/gallon) for test result (Albumin). Could not find valid conversion for matched marker id 195"
]
}
We can see that the test result for Albumin is reported in non-standard units that cannot be converted.
There are two options for how to deal with errors like this when they occur:
- You can choose to accept the results that were able to be imported and ignore those that couldn't.
- You can choose to cancel this upload batch, correct the problem on your side, and then try again in a new batch.
In either case, the approach is the same:
PATCH /api/v1/patients/MRN84327/results-upload-batches/MRN84327_2022_03_19_batch_with_errors
{
"action": "RELEASE"
}
Accepts the result batch "as-is" and releases the successfully imported results for downstream analysis.
or
{
"action": "DELETE"
}
Deletes the upload batch and all of its associated test results, allowing you to try again with a new batch.
# Retrieving the final analysis results and reports
Shortly after the test result batch is released (either manually in the case of errors, or automatically in the case of no errors) the analysis will be completed and available for download through the API. If you closely review the example above for uploading the results batch you'll note the JSON object "patientOrder". When you upload the results you specify which analysis package is to be run and you also assign a unique order identifier which is used to retrieve the results of the analysis.
If you're interested in just the results of the Biological Age analysis you can use the API to retrieve just the test results for the patient order:
GET /api/v1/patients/MRN84327/orders/MRN84327_2022_03_19/results?pageSize=100
{
"pageNumber": 1,
"numberOfElements": 87,
"totalNumberOfElements": 87,
"results": [
...,
{
"name": "Global BioAge",
"identifier": "BioAgeGlobal",
"collectionDate": "2021-12-31",
"specimen": "CALCULATED",
"units": "years",
"value": "47.9",
"valueIsNumeric": true,
"riskClassification": "MINIMAL"
},
{
"name": "Liver BioAge",
"identifier": "BioAgeLiver",
"collectionDate": "2021-12-31",
"specimen": "CALCULATED",
"units": "years",
"value": "51.7",
"valueIsNumeric": true,
"riskClassification": "HIGH"
},
...
}
To retrieve the completed final report for the Biological Age analysis of our example above, it's a two step process:
GET /api/v1/patients/MRN84327/orders/MRN84327_2022_03_19/reports
{
"pageNumber": 1,
"numberOfElements": 1,
"totalNumberOfElements": 1,
"reports": [
{
"id": 309,
"name": "Biological Age",
"type": "patient-facing",
"createdTimestamp": "2022-08-04T22:29:19Z"
}
]
}
The details endpoint retrieves all of the content that can be used to render a customer-facing visual report utilizing the UI-Kit report display widget. It includes test results analysis and interpretation as well as recommended interventions.
GET /api/v1/patients/MRN84327/reports/309/details
{
"id": 309,
"name": "Biological Age",
"type": "patient-facing",
"createdTimestamp": "2022-08-04T22:29:19Z",
"summarySection": {
...
},
"bodySections": [ ... ]
}