Skip to content

Loading One Record by ID

Every T3 collection endpoint has a matching object endpoint: append the record's id to the collection path and you get that one record back, with no pagination envelope around it.

GET https://api.trackandtrace.tools/v2/packages/active/12345?licenseNumber=LIC-00001
{
  "id": 12345,
  "label": "1A4FF0100000022000000123",
  "quantity": 4.5,
  "unitOfMeasureAbbreviation": "g",
  "item": { "name": "Blue Dream Buds", "...": "..." },
  "licenseNumber": "LIC-00001",
  "dataModel": "MetrcPackage",
  "retrievedAt": "2026-09-22T17:42:11.482Z"
}

Compare that to the collection form, which returns the same record wrapped in a page:

{
  "data": [ { "id": 12345, "...": "..." } ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}

An object request is as fast as a collection request, however many records the license has.

Why Use It

  • You already have the id. /v2/search hands back ids, as does every collection response and every report row. The object endpoint is where you spend one.
  • You want one hydrated record. /v2/packages/active/super/12345?include=labResults loads a single package with its lab results attached, far faster than loading includes for a whole supercollection page.
  • You want a real 404. An id that does not exist is a 404, not a 200 with an empty list you have to check for.

Request

Part Notes
id in the path The record's Metrc id, an integer. Non-numeric paths are not object lookups, so /v2/packages/active/super is still the supercollection.
licenseNumber Required, exactly as on the collection.
include Supported on /super paths, with the same values the supercollection accepts. See Supercollection Includes.
collectionMask Supported, and shapes the returned record.

page, pageSize, sort, filter, filterLogic and strictPagination are rejected with a 400. The response is a single record, so there is nothing for them to act on; use the collection endpoint when you want a page.

Responses

Status Meaning
200 The record, as a bare JSON object.
400 A paging, sorting or filtering parameter was sent, or an unrecognized include.
404 No record with that id exists in this collection for this license.

A 404 means "not in this collection". A package that has moved to inactive is a 404 on /v2/packages/active/{id} and a 200 on /v2/packages/inactive/{id}. If you do not know which state a record is in, try each in turn, or use /v2/search to find it first.

Example

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "t3api-utils",
# ]
# ///

from t3api_utils.api.operations import send_api_request
from t3api_utils.main.utils import get_authenticated_client_or_error, pick_license

def main():
    api_client = get_authenticated_client_or_error()
    license_number = pick_license(api_client=api_client)["licenseNumber"]

    # One package, with its lab results, in a single call.
    response = send_api_request(
        api_client,
        "/v2/packages/active/super/12345",
        params={
            "licenseNumber": license_number,
            "include": "labResults",
        },
        expected_status=(200, 404),
        response_type="response",
    )

    if response.status_code == 404:
        print("No active package with that id in this license")
        return

    package = response.json()

    print(package["label"], package["quantity"], package["unitOfMeasureAbbreviation"])
    for result in package["labResults"]:
        print(" ", result["testTypeName"], result["testResultLevel"])

if __name__ == "__main__":
    main()

Going the other way, from a search hit to the full record: the hit's t3EndpointId names the collection, and its object endpoint shares that id. T3 Endpoint IDs lists every collection's id and path.

hit = send_api_request(
    api_client,
    "/v2/search",
    params={"licenseNumber": license_number, "query": "1A4FF0100000022000000123"},
)["data"][0]

assert hit["t3EndpointId"] == "METRC_ACTIVE_PACKAGES"  # /v2/packages/active

package = send_api_request(
    api_client,
    f"/v2/packages/active/{hit['matchedEntry']['id']}",
    params={"licenseNumber": license_number},
)

Every Object Endpoint

Collection Object Super object
Active Packages /v2/packages/active/{id} /v2/packages/active/super/{id}
Inactive Packages /v2/packages/inactive/{id} /v2/packages/inactive/super/{id}
In Transit Packages /v2/packages/intransit/{id} /v2/packages/intransit/super/{id}
On Hold Packages /v2/packages/onhold/{id} /v2/packages/onhold/super/{id}
Transferred Packages /v2/packages/transferred/{id} /v2/packages/transferred/super/{id}
Product Labels /v2/packages/product-labels/{id} /v2/packages/product-labels/super/{id}
Staged Product Labels /v2/packages/staged-product-labels/{id} /v2/packages/staged-product-labels/super/{id}
Lab Samples /v2/packages/lab-samples/{id} /v2/packages/lab-samples/super/{id}
Items /v2/items/{id} /v2/items/super/{id}
Strains /v2/strains/{id} /v2/strains/super/{id}
Active Locations /v2/locations/active/{id} /v2/locations/active/super/{id}
Active Harvests /v2/harvests/active/{id} /v2/harvests/active/super/{id}
Inactive Harvests /v2/harvests/inactive/{id} /v2/harvests/inactive/super/{id}
On Hold Harvests /v2/harvests/onhold/{id} /v2/harvests/onhold/super/{id}
Harvest Schedules /v2/harvests/schedules/{id} (none)
Vegetative Plants /v2/plants/vegetative/{id} /v2/plants/vegetative/super/{id}
Flowering Plants /v2/plants/flowering/{id} /v2/plants/flowering/super/{id}
On Hold Plants /v2/plants/onhold/{id} /v2/plants/onhold/super/{id}
Inactive Plants /v2/plants/inactive/{id} /v2/plants/inactive/super/{id}
Active Mother Plants /v2/plants/mother/active/{id} /v2/plants/mother/active/super/{id}
On Hold Mother Plants /v2/plants/mother/onhold/{id} /v2/plants/mother/onhold/super/{id}
Inactive Mother Plants /v2/plants/mother/inactive/{id} /v2/plants/mother/inactive/super/{id}
Plant Waste /v2/plants/waste/{id} /v2/plants/waste/super/{id}
Plant Additives /v2/plants/additives/{id} /v2/plants/additives/super/{id}
On Hold Plant Batches /v2/plantbatches/onhold/{id} /v2/plantbatches/onhold/super/{id}
Active Sales /v2/sales/active/{id} /v2/sales/active/super/{id}
Inactive Sales /v2/sales/inactive/{id} /v2/sales/inactive/super/{id}
Available Tags /v2/tags/available/{id} /v2/tags/available/super/{id}
Used Tags /v2/tags/used/{id} /v2/tags/used/super/{id}
Voided Tags /v2/tags/voided/{id} /v2/tags/voided/super/{id}
Current Tag Orders /v2/tagorders/current/{id} /v2/tagorders/current/super/{id}
Historical Tag Orders /v2/tagorders/history/{id} /v2/tagorders/history/super/{id}
Active Processing Jobs /v2/processingjobs/active/{id} /v2/processingjobs/active/super/{id}
Inactive Processing Jobs /v2/processingjobs/inactive/{id} /v2/processingjobs/inactive/super/{id}
Incoming Active Transfers /v2/transfers/incoming/active/{id} /v2/transfers/incoming/active/super/{id}
Incoming Inactive Transfers /v2/transfers/incoming/inactive/{id} /v2/transfers/incoming/inactive/super/{id}
Outgoing Active Transfers /v2/transfers/outgoing/active/{id} /v2/transfers/outgoing/active/super/{id}
Outgoing Inactive Transfers /v2/transfers/outgoing/inactive/{id} /v2/transfers/outgoing/inactive/super/{id}
Rejected Transfers /v2/transfers/rejected/{id} /v2/transfers/rejected/super/{id}
Hub Transfers /v2/transfers/hub/{id} /v2/transfers/hub/super/{id}
Transfer Templates /v2/transfers/templates/{id} (none)

The eligibility lookups that feed each write follow the same rule: every source-* collection, such as /v2/packages/create/source-packages or /v2/plants/destroy/source-plants, also answers /{id} with one record. The interactive docs list every one.

Four Exceptions

Plant batches. /v2/plantbatches/active and /v2/plantbatches/inactive have no object endpoint, because those two collections cannot be filtered by id. On hold plant batches are unaffected. To find one active plant batch, page /v2/plantbatches/active and match on id yourself, or filter by name.

Data import templates. /v2/dataimport/templates has no object endpoint, because Metrc ignores an id filter on that collection and returns every template. Page /v2/dataimport/templates and match on id yourself.

Transferred packages. On /v2/packages/transferred, a row's id is the id of the manifest it travelled on, shared by every package on that manifest. The id in the path is therefore the packageId, which is the package's own identity. A package transferred more than once has one row per manifest; the object endpoint returns the first. Use /v2/packages/transferred?filter=packageId__eq:12345 when you need all of them.

Lab samples. /v2/packages/lab-samples lists a sample once per test, and every row carries the sample's package id. The id in the path is therefore the row's labTestDetailId, which names one test on one sample. Use /v2/packages/lab-samples?filter=id__eq:12345 for every test on a sample.

Sub-Collections Are Different

Endpoints that return records belonging to a parent record already take that parent's id as a query parameter, and keep doing so:

GET /v2/packages/history?packageId=12345&licenseNumber=LIC-00001
GET /v2/items/notes?itemId=987&licenseNumber=LIC-00001
GET /v2/transfers/deliveries?manifestNumber=0000123456&licenseNumber=LIC-00001

These have no /{id} form: the id they need is the parent's, not the row's.


Next Steps