Skip to content

Delivery Examples

Recipes for cutting a report down and sending it somewhere: a filtered export, an emailed Excel workbook, a live Google Sheet.

Each uses the same preamble as the first script in Report Examples: a uv script header, imports, and your SECRET_KEY and LICENSE_NUMBER. Scripts authenticate with the X-T3-API-Key header; the plain URLs carry ?secretKey= because they are meant to be pasted somewhere.

Narrowing a report with filters, columns and a row limit

Reports return everything by default, which is rarely what you want while you are still building the request. These three parameters work together to cut a report down:

  • filter narrows which records come back. Repeat it for multiple conditions.
  • columns picks the columns, in the order you list them.
  • rowLimit caps how many records are loaded.

Say you want the label and quantity of every active package in the Bulk Storage room holding more than 100 units:

https://api.trackandtrace.tools/v2/packages/active/report?secretKey=YOUR_SECRET_KEY&licenseNumber=EX-00001&filter=locationName__eq:Bulk Storage&filter=quantity__gte:100&filterLogic=and&columns=label,item.name,quantity,unitOfMeasureAbbreviation&sort=quantity:desc&rowLimit=10
# Same preamble as the first example: uv script header,
# imports, SECRET_KEY and LICENSE_NUMBER.

def main():
    response = httpx.get(
        "https://api.trackandtrace.tools/v2/packages/active/report",
        params={
            "licenseNumber": LICENSE_NUMBER,
            "filter": [
                "locationName__eq:Bulk Storage",
                "quantity__gte:100",
            ],
            "filterLogic": "and",
            "columns": "label,item.name,quantity,unitOfMeasureAbbreviation",
            "sort": "quantity:desc",
            # Start small while you get the filters right, then remove this.
            "rowLimit": 10,
            "contentType": "json",
        },
        headers={"X-T3-API-Key": SECRET_KEY},
        # A report waits up to 240 seconds before giving up.
        timeout=250.0,
    )
    response.raise_for_status()
    report = response.json()

    print(f"{len(report['data'])} packages")

    for package in report["data"]:
        print(package["label"], package["quantity"])

if __name__ == "__main__":
    main()

Build with a small rowLimit, then remove it. See Limiting the Number of Rows.

Note that sort matters once rowLimit is in play: only the first n records are loaded, so sort=quantity:desc&rowLimit=10 gives you the ten largest packages, not ten arbitrary ones.

The column names available for a given report come from its dataModel, which is listed in the metadata preamble and documented under Schemas in the API docs.

Emailing a large report as an Excel workbook

The problem: your report is big enough that the request times out. A normal request waits 240 seconds and then returns Report Generation Timeout.

&delivery=email removes the wait: you get a receipt at once, and the report is sent when it is ready. It must still finish building within about five minutes. Pair it with &contentType=xlsx to get an Excel workbook attached to the email:

https://api.trackandtrace.tools/v2/packages/active/report?secretKey=YOUR_SECRET_KEY&licenseNumber=EX-00001&contentType=xlsx&delivery=email&[email protected]
# Same preamble as the first example: uv script header,
# imports, SECRET_KEY and LICENSE_NUMBER.

def main():
    response = httpx.get(
        "https://api.trackandtrace.tools/v2/packages/active/report",
        params={
            "licenseNumber": LICENSE_NUMBER,
            "contentType": "xlsx",
            "delivery": "email",
            "email": "[email protected]",
        },
        headers={"X-T3-API-Key": SECRET_KEY},
        # A report waits up to 240 seconds before giving up.
        timeout=250.0,
    )
    response.raise_for_status()

    # You get a receipt immediately. The report itself arrives by email.
    print(response.json())

if __name__ == "__main__":
    main()

The receipt, the errors that arrive by email, and why delivery=email never belongs on a Spreadsheet Sync link are covered in Emailing a Report.

Delivering a report to Google Sheets

&contentType=googleSheets builds a Google Sheet. Add &delivery=email and the link is emailed to the account you name instead of returned, which suits a report too large to finish inside a request. (Without delivery=email you get a 302 straight to the sheet; see The googleSheets Format.)

https://api.trackandtrace.tools/v2/packages/active/report?secretKey=YOUR_SECRET_KEY&licenseNumber=EX-00001&contentType=googleSheets&delivery=email&[email protected]
# Same preamble as the first example: uv script header,
# imports, SECRET_KEY and LICENSE_NUMBER.

# Must be a Google account: the sheet is shared with it directly.
GOOGLE_ACCOUNT = "[email protected]"

def main():
    response = httpx.get(
        "https://api.trackandtrace.tools/v2/packages/active/report",
        params={
            "licenseNumber": LICENSE_NUMBER,
            "contentType": "googleSheets",
            "delivery": "email",
            "email": GOOGLE_ACCOUNT,
            # Without this, anyone with the sheet's link can open and edit it.
            "sheetVisibility": "private",
        },
        headers={"X-T3-API-Key": SECRET_KEY},
        # A report waits up to 240 seconds before giving up.
        timeout=250.0,
    )
    response.raise_for_status()

    # The sheet link is emailed once the sheet has been created and shared.
    print(response.json())

if __name__ == "__main__":
    main()

The receipt carries no sheet URL, because the sheet does not exist yet. The recipient must be a Google account, the sheet is public unless you pass sheetVisibility=private, and every request creates a new sheet. See The googleSheets Format.

T3 Reports in the Chrome Extension uses this endpoint, so a sheet it creates behaves the same way.

Next Steps