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.
{
"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
}
Behind the scenes both send exactly one request to Metrc. The object endpoint asks Metrc for the single record matching that id, so it costs the same as any other collection request no matter how many records the license has.
Why Use It¶
- You already have the id.
/v2/searchhands 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=labResultsloads a single package with its lab results attached. The supercollection form costs one Metrc request per include per record, so asking for one record instead of a page of 100 is a hundredfold saving. - You want a real 404. An id that does not exist is a
404, not a200with 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.8"
# dependencies = [
# "t3api-utils",
# ]
# ///
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 = api_client.get(
"/v2/packages/active/super/12345",
params={
"licenseNumber": license_number,
"include": "labResults",
},
)
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:
hit = api_client.get(
"/v2/search",
params={"licenseNumber": license_number, "query": "1A4FF0100000022000000123"},
).json()["data"][0]
# ACTIVE_PACKAGES -> /v2/packages/active
package = api_client.get(
f"/v2/packages/active/{hit['matchedEntry']['id']}",
params={"licenseNumber": license_number},
).json()
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} | (none) |
| 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} |
| 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) |
| Data Import Templates | /v2/dataimport/templates/{id} | (none) |
The modal source lookups follow the same rule:
| Collection | Object |
|---|---|
| Packages eligible to source a package | /v2/packages/create/source-packages/{id} |
| Items eligible to source a package | /v2/packages/create/source-items/{id} |
| Tags eligible for a new package | /v2/packages/create/source-tags/{id} |
| Packages eligible to source a testing package | /v2/packages/create/testing/source-packages/{id} |
| Items eligible to source a testing package | /v2/packages/create/testing/source-items/{id} |
| Tags eligible for a new testing package | /v2/packages/create/testing/source-tags/{id} |
| Packages eligible for adjustment | /v2/packages/adjust/source-packages/{id} |
| Harvests eligible to source a package | /v2/harvests/create/packages/source-harvests/{id} |
| Items eligible for a package from a harvest | /v2/harvests/create/packages/source-items/{id} |
| Tags eligible for a package from a harvest | /v2/harvests/create/packages/source-tags/{id} |
| Destinations eligible for a new transfer | /v2/transfers/create/destinations/{id} |
| Transporters eligible for a new transfer | /v2/transfers/create/transporters/{id} |
| Packages eligible for a new transfer | /v2/transfers/create/packages/{id} |
Two Exceptions¶
Plant batches. /v2/plantbatches/active and /v2/plantbatches/inactive have no object endpoint. Metrc answers an id filter on those two collections with an error, and a plant batch record carries no other unique field to look it up by. 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.
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.
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¶
- Find a record when you do not have its id: Search.
- Attach related records to the one you loaded: Supercollections and Supercollection Includes.
- Load many records at once: Reports.