Skip to content

Shaping a Report

Filtering and Sorting

A report takes filters, a sort and a column list, and returns csv, json, xlsx or googleSheets. See Choosing a Response Format for the formats.

  • If you wanted to only return packages in the Bulk Storage room, you would add &filter=locationName__eq:Bulk Storage to your Sync Link
  • If you wanted to control which columns you want in the report, you would add &columns=label,productionBatchNumber,labTestingStateName to your Sync Link

A filter is spelled field__operator:value. Repeat filter to apply more than one, and set filterLogic to and (the default) or or to say how they combine:

Operator Meaning
eq equals
neq not equals
contains contains
doesnotcontain does not contain
startswith starts with
endswith ends with
lt less than
lte less than or equal
gt greater than
gte greater than or equal

A collection's /fields endpoint lists the fields Metrc's table offers and the operators each one takes. See Filterable Fields.

Sorting takes a single field and direction, &sort=label:asc or &sort=label:desc, and orders rows within each license rather than across them.

Each report's full parameter reference is in the API documentation.

Limiting the Number of Rows

Reports return the entire matching dataset by default, which can be very large. Add &rowLimit=100 to your Sync Link to cap how many records come back.

Start with a small rowLimit while you are still building the request. &rowLimit=10 returns in a second or two with the same columns and formatting as the full report, so you can check your filters and columns before you drop the parameter for the real run.

  • The minimum is 1. Omit rowLimit to return everything.
  • Every report also enforces a hard cap of its own: 50,000 records for a report, 5,000 for a super report. A rowLimit above that cap is clamped down to it, which is the same as leaving rowLimit off.
  • Both the cap and rowLimit are totals across every requested license, not per-license allowances. See Reporting Across Multiple Licenses.
  • A rowLimit can rescue a request that would otherwise be rejected as too large. A license with 100,000 active packages will normally fail, but &rowLimit=500 succeeds and returns 500 packages.
  • Which records you get depends on the sort order. Pair rowLimit with sort for a predictable result, for example &sort=label:asc&rowLimit=100.
  • The limit counts top-level records. Reports that expand one record into several rows, such as the transfer manifest reports, or a super report cross-joined on an include in CSV format, can return more rows than the limit. On a super report, rowMode=collapsed removes that gap: one row per record means the limit counts rows exactly.
  • Changing rowLimit loads fresh data, because it changes which records are fetched. Changing columns, contentType, columnHeaderOverrides or another output option does not: re-requesting with different columns is served from cache and returns immediately.

Specifying Field Names

Every report returns one data model, and its fields are the columns you can ask for. Active packages are MetrcPackage. The name is on the Data model: row of the metadata preamble, and in the dataModel field of every JSON record.

To list a data model's columns, open Schemas at the bottom of the API docs and expand it. A misspelled name also returns an error listing every valid one. See Invalid columns.

To show License Number, Facility Name, Label and Production Batch Number, add:

https://api.trackandtrace.tools/v2/packages/active/report?licenseNumber=...&columns=licenseNumber,facilityName,label,productionBatchNumber

These are returned as the column headers, in the order you list them. In CSV output the headers are title-cased from the field names, so productionBatchNumber appears as Production Batch Number.

columns was previously called fieldnames

Both spellings work and mean exactly the same thing, so existing Sync Links and saved reports keep running untouched; there is nothing you need to change. fieldnames is deprecated and columns is the name documented from here on, so prefer it for anything new. Sending both in one request returns a 400, since there is no way to tell which column list you meant.

Packages nest their item. Prefix an item field with item. to use it, for example item.productCategoryName for the item category:

https://api.trackandtrace.tools/v2/packages/active/report?licenseNumber=...&columns=licenseNumber,facilityName,label,productionBatchNumber,item.productCategoryName

Four nested item fields are available this way: item.name, item.strainName, item.productCategoryName and item.unitThcContent. Packages are the only Metrc object with nesting like this, and item on its own is not a valid column.

The transfer manifest reports prefix every column with the object it came from. See Transfer manifest reports work differently.

Renaming the Column Headers

Headers are title-cased from the field names, which is not always the name you want. Add &columnHeaderOverrides= to replace them with your own.

The value is a comma-separated list matched positionally against the columns, and an empty entry keeps that column's default name. So to rename only the fourth column, count three commas first:

https://api.trackandtrace.tools/v2/packages/active/report?licenseNumber=...&columns=label,item.name,quantity,unitOfMeasureAbbreviation&columnHeaderOverrides=,,,Unit
Label,Item Name,Quantity,Unit

Trailing entries can be dropped entirely, so &columnHeaderOverrides=Tag renames just the first column and leaves the rest alone.

This is most useful on the transfer manifest reports, whose columns carry an object prefix that reads awkwardly once title-cased:

Column Default header With an override
package.packageLabel Package Package Label Package Tag
transfer.manifestNumber Transfer Manifest Number Manifest
transporterDetails.driverVehicleLicenseNumber Transporter Details Driver Vehicle License Number Plate

Names are used exactly as you send them. No title-casing, no trimming, no change of case: qty stays qty. That is what makes it safe to point a pivot table, a Power Query step or a partner's import template at a header. A name beginning with = is written as text, never as a live spreadsheet formula.

Two rules to know:

  • A header cannot contain a comma. A comma always starts a new entry, even encoded as %2C.
  • You can send fewer entries than there are columns, but not more. Extra entries return a 400. Trailing commas count as entries: ,,,Unit,, is six of them.

The parameter applies to the tabular formats: csv, xlsx and googleSheets. A json report returns records keyed by their field names and is unaffected, so you can set it once alongside a contentType that varies. It also cannot be combined with transform, which names its own columns.

Renaming a header does not change which data is loaded, so switching headers on a report you just pulled is served from cache and returns immediately.

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.8"
# dependencies = [
#     "httpx",
# ]
# ///
"""Pull an incoming manifest report with headers a warehouse team will recognize."""

import httpx

SECRET_KEY = "YOUR_SECRET_KEY_GOES_HERE"
LICENSE_NUMBER = "LIC-00001"

COLUMNS = [
    "transfer.manifestNumber",
    "transfer.recipientFacilityName",
    "package.packageLabel",
    "package.productName",
    "package.shippedQuantity",
]

HEADERS = ["Manifest", "Destination", "Tag", "Product", "Qty"]

response = httpx.get(
    "https://api.trackandtrace.tools/v2/transfers/incoming/manifest/report",
    params={
        "licenseNumber": LICENSE_NUMBER,
        "contentType": "csv",
        "columns": ",".join(COLUMNS),
        "columnHeaderOverrides": ",".join(HEADERS),
        "prependCsvMetadata": "false",
    },
    headers={"X-T3-API-Key": SECRET_KEY},
    # A report waits up to 240 seconds before giving up.
    timeout=250.0,
)
response.raise_for_status()

print(response.text.splitlines()[0])
# Manifest,Destination,Tag,Product,Qty

Next Steps