Supercollection Includes¶
The include parameter controls which additional data is fetched and attached to each object in the response. Without it, a supercollection request behaves like a regular collection request.
flowchart LR
P["Superpackages"] --> PA["labResults"]
P --> PB["labResultBatches"]
P --> PC["sourceHarvests"]
P --> PD["history"]
P --> PE["coaResults"]
H["Superharvests"] --> HA["history"]
H --> HB["plants"]
H --> HC["packages"]
T["Supertransfers:<br/>incoming, outgoing, rejected"] --> TA["packages"]
T --> TB["transporters"]
TH["Hub supertransfers"] --> THA["packages"]
TH --> THB["history"]
I["Superitems"] --> IA["notes"]
I --> IB["history"]
I --> IC["ingredients"]
I --> ID["images"]
PL["Superplants and<br/>superplantbatches"] --> PLA["history"]
SA["Supersales"] --> SAA["transactions"] Each include value does two things:
- Attaches raw Metrc data as a new array on each object (e.g.
labResults,sourceHarvests). - Populates fields on the
metadataobject with extracted, cleaned-up values derived from that raw data.
You can combine multiple include values by repeating the parameter: include=labResults&include=sourceHarvests.
A comma-joined list is not supported, include=labResults,sourceHarvests is one value, not two.
An include value the endpoint does not accept returns 400 Invalid Include, and the response lists the values it does accept:
GET /v2/packages/active/super?licenseNumber=EX-00001&include=labresults
400 Invalid Include
Unrecognized include `labresults`. This endpoint accepts: history, sourceHarvests,
labResultBatches, labResults.
Include values are case-sensitive, so labresults above is rejected while labResults is accepted. The valid values for each endpoint are also published as an enum in the OpenAPI spec, so an API client can read them programmatically rather than copying them from this page.
The same include values work on super reports, which return the whole enriched dataset in one request instead of paging through it, useful for spreadsheet exports. See Super Reports.
A few metadata fields are populated unconditionally (no include required), when the underlying data is available:
- Superpackages:
netWeightOrVolume,netWeightOrVolumeUnitOfMeasure - Superitems:
approvalNumber,itemImages
Superpackage includes¶
include | Raw data attached | metadata fields populated |
|---|---|---|
labResults | labResults[] | extractedLabResults, testSamplePackageLabels, labResultPdfs, indexedLabResults, hasTerpeneResults |
labResultBatches | labResultBatches[] | extractedLabResults, testSamplePackageLabels, labResultPdfs, indexedLabResults, hasTerpeneResults |
sourceHarvests | sourceHarvests[] | harvestDates |
history | history[] | initialQuantity, initialQuantityUnitOfMeasure, quantityLedger, manifestNumber², destinationFacilityLicenseNumber², destinationFacilityName², transferType² |
coaResults | coaResults[], labResults[] | indexedCoaResults, coaDocuments, and everything labResults populates |
² Only present for in-transit packages.
indexedLabResults is a simplified dictionary intended for templating (e.g. on a label). Example access:
{{ package.metadata.indexedLabResults.totalTHC.name }} → "Total THC"
{{ package.metadata.indexedLabResults.totalTHC.value }} → 12.5
{{ package.metadata.indexedLabResults.totalTHC.unit }} → "%"
{{ package.metadata.indexedLabResults.totalTHC.full }} → "Total THC: 12.5 %"
Each test has one key in every state, however the lab spelled it: Total THC and Total Delta-9 THC are both totalTHC, and THCa is THCA. name and full keep the lab's own spelling. Lab Result Keys lists every key and the names it covers. Keys from before this naming, such as totalDelta9THC or Total_THC, still work in label templates and report columns.
When a package reports a test more than once, the entry holds one result: a real result over a lab quality-control figure, then % over any other unit, then a nonzero value over zero, then the most recent test.
byUnit keeps one result per unit, so an edible's per-serving and per-package values are both available:
{{ package.metadata.indexedLabResults.totalTHC.byUnit.mgPerServing.full }} → "Total THC: 10.0 mg/serving"
{{ package.metadata.indexedLabResults.totalTHC.byUnit.mgPerPackage.full }} → "Total THC: 100.0 mg/package"
Unit keys are the unit in camelCase: % is percent, mg/g is mgPerG, mg/serving is mgPerServing.
Also indexed: topTerpene1, topTerpene2, etc.
topTerpene1 and its siblings rank the package's individual terpenes by descending value. Rollup results a lab reports alongside them -- Total Terpenes, Other Terpenes -- are excluded, since they summarize the individual terpenes rather than being one. Those rollups are still indexed under their own keys, totalTerpenes and otherTerpenes.
Because the topTerpeneN keys only exist when the package actually has terpene results, metadata.hasTerpeneResults tells you whether any are there. Use it to toggle a whole terpene section rather than probing for topTerpene1:
{% if package.metadata.hasTerpeneResults %}
Terpenes
{{ package.metadata.indexedLabResults.topTerpene1.full }} → "Limonene: 1.2 %"
{{ package.metadata.indexedLabResults.topTerpene2.full }} → "Myrcene: 0.8 %"
{% endif %}
hasTerpeneResults is always present, and is true exactly when topTerpene1 is. A package whose terpenes were all measured at zero still reports true -- those results were reported and are indexed, so the section has rows to show.
COA results¶
coaResults reads results out of the packages' COA PDFs. COAs are read ahead of time from a tag list; see Extracting COA Results. If any package on the page has a COA that has not been read yet, the request fails with 409 COA_EXTRACTION_REQUIRED and returns nothing.
{{ package.metadata.indexedCoaResults.totalTHC.full }} → "Total THC: 25.32 %"
{{ package.metadata.indexedCoaResults.delta9THC.byUnit.mgPerG.value }} → 4.106
{{ package.metadata.coaDocuments[0].publicPdfUrl }} → "https://api.trackandtrace.tools/v2/coa/3f9c2a1e-....pdf"
Keys, byUnit and the top-level result work exactly as in indexedLabResults, so a template can switch between the two by changing one word. When a package has several COAs, the most recently tested one wins each result and older ones fill in what it lacks. A non-detect has value 0 and a qualifier such as ND, which full shows in place of the value: "Lead: ND ppm".
coaDocuments lists each COA behind the results with its status (READY or FAILED) and a publicPdfUrl that needs no login. In a CSV report, coaResults[] gives one row per result.
Quantity ledger¶
include=history also fills metadata.quantityLedger: how the package's quantity changed over time, read from its history. It has one entry per history line that states or changes the quantity, oldest first. Moves, manifests, lab results and finishes are left out.
[
{
"historyIndex": 0,
"kind": "created",
"actualDate": "2025-09-15",
"recordedDateTime": "2025-09-15T16:44:14.6429877Z",
"quantityChange": 24,
"quantityAfter": 24,
"unitOfMeasureAbbreviation": "ea"
},
{
"historyIndex": 2,
"kind": "accepted",
"actualDate": null,
"recordedDateTime": "2025-09-16T18:01:15.532948Z",
"quantityChange": 0,
"quantityAfter": 24,
"unitOfMeasureAbbreviation": "ea"
},
{
"historyIndex": 4,
"kind": "sold",
"actualDate": "2025-09-23",
"recordedDateTime": "2025-09-23T18:50:42.826531Z",
"quantityChange": -1,
"quantityAfter": 23,
"unitOfMeasureAbbreviation": "ea"
}
]
| Field | Meaning |
|---|---|
historyIndex | Position of the source entry in history[]. Use it to read the full text, the user, a manifest or a receipt. |
kind | created, used, restored, adjusted, unpacked, sold, saleRemoved, saleReturned, accepted or unrecognized |
actualDate | The date the user gave. Null for accepted. |
recordedDateTime | When Metrc recorded it |
quantityChange | How much the quantity moved. Negative for a decrease. |
quantityAfter | The quantity once this entry was recorded |
unitOfMeasureAbbreviation | The unit of both quantities |
Entries are in the order Metrc recorded them, so quantityAfter is what Metrc showed at that moment. The last entry's quantityAfter is the package's quantity.
acceptedrestates the quantity when a transfer is received, so its change is normally 0.- An entry can be recorded later than its
actualDate. Sort byactualDatefor a dated view. quantityAftercan be negative. Metrc lets more be taken from a package than it holds.- A package's history follows it between licenses, so the ledger includes changes made before your license received it.
unrecognizedis a history line that could not be read. Its change and balance are null, and later balances do not account for it.
This prints the quantity each package had when 2025-10-10 began, UTC:
import requests
response = requests.get(
"https://api.trackandtrace.tools/v2/packages/active/super",
headers={"Authorization": f"Bearer {access_token}"},
params={"licenseNumber": "DIS000038", "include": "history"},
)
for package in response.json()["data"]:
ledger = package["metadata"].get("quantityLedger", [])
earlier = [entry for entry in ledger if entry["recordedDateTime"] < "2025-10-10"]
quantity = earlier[-1]["quantityAfter"] if earlier else 0
print(package["label"], quantity, package["unitOfMeasureAbbreviation"])
The ledger is a nested list, so it is meant for JSON responses.
Superitem includes¶
include | Raw data attached | metadata fields populated |
|---|---|---|
notes | notes[] | (none) |
history | history[] | (none) |
ingredients | ingredients[] | (none) |
images | images[] | itemImages |
approvalNumber and itemImages are populated on every superitem when the data is available, you don't need include=images to get image metadata, only the raw base64/image records themselves.
Supertransfer includes¶
include | Raw data attached | metadata fields populated |
|---|---|---|
packages | packages[] | (none) |
transporters | transporters[] | (none) |
Hub supertransfer includes¶
Hub transfers (the ones your license transports) take a different include set:
include | Raw data attached | metadata fields populated |
|---|---|---|
packages | packages[] | (none) |
history | history[] | (none) |
There is deliberately no transporterDetails include. Metrc embeds the driver legs directly in the hub response as shipmentTransporterDetails, so they are present on every record without spending a request:
import requests
response = requests.get(
"https://api.trackandtrace.tools/v2/transfers/hub/super",
headers={"Authorization": f"Bearer {access_token}"},
params={
"licenseNumber": "DIS000038",
"pageSize": 20,
"include": ["packages", "history"],
},
)
for transfer in response.json()["data"]:
# Already present -- no include was needed for this
for leg in transfer["shipmentTransporterDetails"]:
print(transfer["manifestNumber"], leg["lineNumber"], leg["driverName"])
# These two required the includes above
print(f" {len(transfer['packages'])} packages, {len(transfer['history'])} history entries")
Note that transporterFacilityName, driverName, vehicleMake and friends are also flattened onto the transfer itself. When multiVehicle or isLayover is true those top-level fields describe only the first leg, read shipmentTransporterDetails for the rest.
Superharvest includes¶
include | Raw data attached | metadata fields populated |
|---|---|---|
history | history[] | (none) |
plants | plants[] | (none) |
packages | packages[] | (none) |
Superplant includes¶
include | Raw data attached | metadata fields populated |
|---|---|---|
history | history[] | (none) |
Supersales includes¶
include | Raw data attached | metadata fields populated |
|---|---|---|
transactions | transactions[] | (none) |
Superprocessingjob includes¶
Processing jobs are currently exposed by Metrc in Oregon only.
include | Raw data attached | metadata fields populated |
|---|---|---|
createdPackages | createdPackages[] | (none) |
sourcePackages | sourcePackages[] | (none) |
history | history[] | (none) |
createdPackages are the packages the job produced; sourcePackages are the packages it consumed. Both return the same shape.
A job record also arrives with a packages array already populated. That is Metrc's own partial projection of the source packages, and every entry in it carries "processingJobId": 0 instead of the real job id, so it cannot be joined back to the job it came from. Use include=sourcePackages when you need those packages:
GET https://api.trackandtrace.tools/v2/processingjobs/active/super
?licenseNumber=LIC000001
&include=sourcePackages
{
"number": "0001465709",
"jobTypeName": "Cannabis Extract Job",
"totalQuantity": 237.36,
"isFinished": false,
"sourcePackages": [
{
"processingJobId": 1465709,
"label": "1A4010300002A31000018335",
"quantity": 18.35,
"unitOfMeasureAbbreviation": "lb",
"itemName": "PERMANENT LEE HI FLOWER"
}
],
"metadata": {
"summary": "0001465709 · In progress · Cannabis Extract Job",
"description": "Processing job 0001465709. Type: Cannabis Extract Job. Consumed: 12 packages."
}
}
Next Steps¶
- Back to the endpoint list: Supercollections.
- Use includes in a report instead: Super Reports.