Output and Delivery Format¶
Choosing a Response Format¶
Add &contentType= with one of four values:
contentType | You get | Includes | Use it for |
|---|---|---|---|
json | Nested JSON envelope | Unlimited | Scripts and integrations |
csv | .csv file download | One | Spreadsheets, scheduled pulls, anything automated |
xlsx | .xlsx file download | One | Opening in Excel without an import step |
googleSheets | A redirect to a new Google Sheet | One | One-off exports you intend to share or edit |
json and csv can also be set with the Content-Type header. xlsx and googleSheets are query-parameter only.
csv, xlsx and googleSheets contain exactly the same thing, same rows, same title-cased headers, same metadata preamble. Only the container differs. All three of json, csv and xlsx download as file attachments. See Downloading vs. Rendering.
A JSON report returns a single object describing both the request and the results:
{
"generatedAt": "2024-01-01T00:00:00+00:00",
"filters": ["quantity__gte:100"],
"filterLogic": "and",
"sort": "label:asc",
"licenseNumbers": ["LIC-00001"],
"data": [ ... ]
}
The data array holds the records. The other fields echo back the request that produced them, which is useful when you are storing report output and need to know later how it was generated.
Downloading vs. Rendering¶
Reports download by default. Open a report URL in a browser and you get a saved file rather than a wall of text, for json, csv and xlsx alike.
If you are writing a script, this changes nothing. Content-Disposition is a hint to browsers; curl, Python's requests, JavaScript's fetch and Google Sheets IMPORTDATA all ignore it and hand you the same bytes they always did.
Add &contentDisposition=inline when you do want a browser to display the response instead, reading a JSON report without saving it, or eyeballing a CSV in a tab:
# Saves a file
https://api.trackandtrace.tools/v2/packages/active/report?licenseNumber=LIC-00001
# Displays in the browser
https://api.trackandtrace.tools/v2/packages/active/report?licenseNumber=LIC-00001&contentDisposition=inline
contentType=googleSheets redirects to a spreadsheet rather than returning a file, so it ignores this parameter.
The filename describes the report, the license, the date it was generated, and a short hash of the request:
A report covering several licenses names the first and counts the rest, LIC-00001-plus-3. The hash on the end is what stops two variants of the same report, different columns, a different filter, from overwriting each other in your downloads folder. A scan sheet names itself instead.
The Metadata Preamble¶
By default every tabular report (csv, xlsx and googleSheets alike) starts with eight rows describing the request, before the column headers:
Report:,ACTIVE_PACKAGES_REPORT
Data model:,MetrcPackage
Generated at:,2024-01-01T00:00:00+00:00
License numbers:,LIC-00001
Filters:,quantity__gte:100
Filter logic:,and
Sort:,label:asc
Label,Location Name,Item Name,Quantity,Unit Of Measure Abbreviation
1A4400000000000000001234,Room A,Blue Dream,10,ea
So the column headers land on row 9, and your first data row is on row 10. The example above is CSV, but xlsx and googleSheets put the same values in the same cells, labels in column A, values in column B.
A multi-license report lists them comma-joined in that one cell, License numbers:,LIC-00001, LIC-00002, so the preamble stays eight rows and the header stays on row 9 however many licenses you request.
Add &prependCsvMetadata=false to remove the preamble entirely. Headers then start on row 1 and data on row 2. The parameter keeps its Csv name for backwards compatibility, but it governs all three tabular formats.
Which should you use?
- Keep the preamble when a person will open the file and needs to know which license, filters and sort produced it.
- Turn it off when the output feeds a formula or query that expects headers first. This is usually what you want with
IMPORTDATAin Google Sheets or Power Query in Excel, since otherwise every formula referencing the sheet has to be offset by eight rows.
Reporting Across Multiple Licenses¶
Reports are the only endpoints that accept more than one license. Repeat licenseNumber to pull several into one document:
https://api.trackandtrace.tools/v2/packages/active/report?licenseNumber=LIC-00001&licenseNumber=LIC-00002
A comma-joined list does not work, repeat the parameter. Repeating the same license returns a 400, and at most 20 licenses may be requested at once.
Rows come back in the order you list the licenses. Every row of LIC-00001 precedes every row of LIC-00002. sort orders rows within each license, not across them.
Add licenseNumber to your columns so you can tell the rows apart. Every record already carries it, so the column costs nothing extra:
...&licenseNumber=LIC-00001&licenseNumber=LIC-00002&columns=licenseNumber,label,quantity
It is not a default column, so single-license reports are unchanged. On the transfer manifest reports the field is transfer.licenseNumber, because those rows are built from a transfer/delivery/package hierarchy.
The limits are totals, not per-license allowances. This is the part worth reading twice:
- The row cap (50,000 for a report, 5,000 for a super report) applies to the sum. Two licenses holding 30,000 records each are 60,000 records, and the request is rejected. The error names each license's count so you can see which one to filter.
rowLimitis a total: filled in license order.&rowLimit=600against a first license holding 400 records returns all 400 of the first and the first 200 of the second, and never asks Metrc for the rest of the second.- The 10,000 Metrc request budget is likewise one total across all licenses.
A report is all-or-nothing
If any license fails, most often because your account cannot access it; the whole request fails. A partial report would look complete, and a spreadsheet has no way to notice the difference.
A multi-license request counts as one report request against your rate limits, no matter how many licenses it names.
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "httpx",
# ]
# ///
"""Pull active packages across several licenses into one CSV."""
import csv
import io
import httpx
SECRET_KEY = "YOUR_SECRET_KEY"
LICENSE_NUMBERS = ["EX-00001", "EX-00002"]
response = httpx.get(
"https://api.trackandtrace.tools/v2/packages/active/report",
params=[
("contentType", "csv"),
("columns", "licenseNumber,label,quantity"),
("prependCsvMetadata", "false"),
# Repeat the parameter, once per license. httpx serializes a list of
# tuples as licenseNumber=EX-00001&licenseNumber=EX-00002, which is
# exactly what the endpoint expects.
*[("licenseNumber", x) for x in LICENSE_NUMBERS],
],
headers={"X-T3-API-Key": SECRET_KEY},
timeout=120.0,
)
response.raise_for_status()
rows = list(csv.DictReader(io.StringIO(response.text)))
# Rows arrive grouped by license, in the order the parameters were sent.
for license_number in LICENSE_NUMBERS:
matching = [x for x in rows if x["License Number"] == license_number]
print(f"{license_number}: {len(matching)} packages")
print(f"total: {len(rows)}")
Next Steps¶
- Email a report or push it to Google Sheets in Delivering a Report.
- Cut the dataset down first with Shaping a Report.
- See a multi-license export end to end in Report Examples.