How to Pull Lab Results from the Metrc API

Lab results drive labels, certificates of analysis, product listings, and quality checks, which makes them some of the most requested data in Metrc. They are also some of the hardest to work with, because every lab names its tests a little differently. This guide covers how to pull lab results through the T3 API and how normalized keys make the values usable.

Updated

How lab results are structured

Testing laboratories record results in Metrc, and those results are attached to the packages that were tested. A single package’s results usually span many rows, one per analyte per test, and often 50 to 100 of them. In the T3 API each row includes fields such as testTypeName, testPassed, testResultLevel, overallPassed, labFacilityName, and testPerformedDate.

The hard part is naming. One lab reports “Total THC” while another reports “Total Delta-9 THC”, and THCa can appear as THCA, THC-A, or Delta-9 THCA. Code that matches raw test names ends up with an ever-growing list of special cases.

Option 1: Results for a single package

When you already know the package, request its results directly. Each of these endpoints takes a licenseNumber plus the package or harvest ID:

  • GET /v2/packages/labresults returns the individual lab result rows for a packageId.
  • GET /v2/packages/labresult-batches returns the same package’s results grouped into lab result batches.
  • GET /v2/packages/labresults/document returns the COA PDF for a labTestResultDocumentFileId. A package may have hundreds of results, but most share just one or two document IDs.
  • GET /v2/harvests/labresults returns results for a harvestId.

These IDs are numeric Metrc IDs, not tag labels. If you only have a tag, look up the package first by filtering the collection, for example filter=label__eq:1A4000000000000000000001.

Option 2: Results on every package with include=labResults

For inventory-wide work, call a packages supercollection such as /v2/packages/active/super (on hold, inactive, and in transit packages work the same way) with include=labResults, or include=labResultBatches:

Request
GET https://api.trackandtrace.tools/v2/packages/active/super
  ?licenseNumber=LIC-000123
  &include=labResults
X-T3-API-Key: <your secret key>

Each package comes back with its raw labResults array and a metadata object populated with derived fields:

  • extractedLabResults: every result with its value, unit, pass status, and normalized tags such as ["total", "thc"].
  • testSamplePackageLabels: the test sample packages associated with this package.
  • labResultPdfs: T3 API URLs to the package’s COA PDFs, readable with an authenticated request.
  • indexedLabResults and hasTerpeneResults, covered below.

Normalized keys with metadata.indexedLabResults

indexedLabResults is a simplified dictionary with one entry per test, keyed by a normalized name that is the same in every state no matter how the lab spelled it:

Response excerpt
"metadata": {
  "indexedLabResults": {
    "totalTHC": {
      "full": "Total THC: 22.4 %",
      "name": "Total THC",
      "value": 22.4,
      "unit": "%"
    },
    "totalCBD": {
      "full": "Total CBD: 0.1 %",
      "name": "Total CBD",
      "value": 0.1,
      "unit": "%"
    },
    "topTerpene1": {
      "full": "Myrcene: 1.2 %",
      "name": "Myrcene",
      "value": 1.2,
      "unit": "%"
    }
  },
  "hasTerpeneResults": true
}

How keys are formed

  • “Total THC” and “Total Delta-9 THC” both become totalTHC, and THCa becomes THCA.
  • Listed tests use camelCase keys with acronyms kept in capitals, such as totalCBD, delta9THC, CBG, and betaMyrcene.
  • A test that is not on the list is still indexed, under its name in lowercase with everything but letters and digits removed.
  • Keys are case-sensitive in JSON, so match them exactly.

The full list of keys, covering cannabinoids, terpenes, residual solvents, and more, lives on the Lab Result Keys reference.

What each entry contains

FieldMeaning
fullName, value, and unit combined, such as “Total THC: 22.4 %”
nameThe test name as the lab printed it
valueThe numeric value when available; may be a string such as “ND” or “<LOQ” for non-detects
unitThe measurement unit, such as %, mg/g, or ppm
byUnitThe winning result for each unit the test was reported in, keyed percent, mgPerG, mgPerServing, mgPerPackage, or ppm

When a test is reported more than once

Each entry holds a single result, chosen in this order: a real result over a lab quality-control figure (RPD), then a % result over any other unit, then a nonzero value over zero, then the most recently performed test. Because byUnit keeps the winner for every unit, a per-serving and a per-package result both stay reachable.

Top terpenes

topTerpene1, topTerpene2, and so on rank the package’s individual terpenes by descending value. Rollups such as Total Terpenes and Other Terpenes are excluded, and byUnit is absent on these entries. hasTerpeneResults is true exactly when topTerpene1 is present, so you can toggle a terpene section without probing for numbered keys.

python
indexed = package["metadata"].get("indexedLabResults", {})

thc = indexed.get("totalTHC")
if thc:
    print(thc["full"])  # "Total THC: 22.4 %"

per_serving = thc and thc.get("byUnit", {}).get("mgPerServing")
if per_serving:
    print(per_serving["value"], per_serving["unit"])

if package["metadata"].get("hasTerpeneResults"):
    print("Top terpene:", indexed["topTerpene1"]["name"])

Lab results in reports

To export lab values for a whole license, use a super report with include=labResults and rowMode=collapsed, which keeps one row per package instead of one row per analyte, then select the metadata columns you need:

Potency export as CSV
GET https://api.trackandtrace.tools/v2/packages/active/super/report
  ?licenseNumber=LIC-000123
  &include=labResults
  &rowMode=collapsed
  &columns=label,item.name,metadata.indexedLabResults.totalTHC.value,metadata.indexedLabResults.totalCBD.value
  &contentType=csv

Each include costs one Metrc request per record, and super reports cap at 5,000 rows, so filter to the packages you care about. The export guide covers formats, email delivery, and Spreadsheet Sync.

Print lab values on labels

Compliance labels frequently need potency and terpene values, and T3 label templates read the same keys. Design templates visually in T3 Label Studio, or generate PDFs from your own systems with POST /v2/labels/generate, passing each package object in labelContentDataList. Inside a template, lab values are one expression away:

Label template
{{ package.metadata.indexedLabResults.totalTHC.full }}
{{ package.metadata.indexedLabResults.totalCBD.full }}

{% if package.metadata.hasTerpeneResults %}
  Terpenes
  {{ package.metadata.indexedLabResults.topTerpene1.full }}
  {{ package.metadata.indexedLabResults.topTerpene2.full }}
{% endif %}

Watermarks and T3+

Labels generated on the free tier carry a watermark, which a T3+ subscription removes. To see how lab results fit with labels, transfers, and reporting, explore what the T3 API can do.

The best way to talk to Metrc

The T3 API gives developers one consistent REST interface to Metrc data across every Metrc state, with interactive OpenAPI documentation, predictable JSON, and reports that export full datasets in a single request.

Track & Trace Tools is not affiliated with Metrc.