Skip to content

Tag Lists


Overview

A tag list is a named, ordered set of Metrc tag handles with per-entry metadata. Tag lists are a T3-side grouping construct — they live inside the T3 platform and do not read from or write back to Metrc on their own.

Tag lists are the primary unit of work in OpenTag and are also exposed as a set of endpoints in the T3 API, so the same list can be built on a phone, queried from a script, or edited from either.


How Tag Lists Are Built

Tag lists can be built in two ways.

One at a time. Start with an empty list, then append entries as you encounter them — for example, scanning package tags with a phone or an RFID reader, or POSTing entries to the API one by one as they come in from another system.

Bulk-generated from an existing source. Populate a list in a single request from data you already have — for example, the package list from a transfer manifest, the rows of a Metrc report, or a CSV of handles. A list can also be seeded with initial entries at creation time and appended to later.

The two paths are not mutually exclusive. A common pattern is to seed a list from a manifest, then scan physical tags against it to verify the shipment.


How Tag Lists Are Used

Checking Off Inventory

Each entry has a nullable scannedAt timestamp. Marking an entry as scanned — setting scannedAt to the current time — is how tag lists record that a physical tag has been accounted for. Unmarking simply sets scannedAt back to null.

The standard workflow:

  1. Create a list and populate it with the tags you expect to see.
  2. As you scan, either PATCH individual entries or — for a batch of handles like an RFID sweep — hit the bulk scan endpoint (POST /v2/tag-lists/{id}/scan). The bulk endpoint accepts an array of handles, stamps every matching entry in one call, and returns any supplied handles that weren't in the list.
  3. Read the list back. The detail endpoint's meta.scannedCount tells you how many entries are accounted for; the list endpoint returns the same summary without pulling every entry across the wire.

Membership Lookup

Given a single tag handle, is it in this list? This is the second core use case: reading back a list and checking whether a specific handle appears in entries. This powers workflows like "did this package ship on manifest X?" or "is this plant part of the harvest batch I'm auditing?".

Other Use Cases

Beyond inventory check-offs and membership, tag lists are useful for:

  • Reconciliation. Build a list of what Metrc says is on hand, scan what's physically present, and diff the two.
  • Pre-transfer manifest building. Assemble the set of packages intended for an outgoing transfer on-device or via API, then hand that set off to the manifest-creation step.
  • Audit trails. Per-entry scannedAt timestamps plus arbitrary metadata give you a dated record of who touched what, which survives after the physical work is done.
  • Feeding downstream T3 workflows. A tag list is a convenient input for other T3 features — for example, generating labels for every tag in a list, or filtering a report down to just the handles you care about.

Anatomy of a Tag List

A tag list has two layers: the list itself, and the entries inside it.

List-level fields:

  • id — numeric identifier.
  • name — optional human-readable label ("Room A inventory", "Manifest M-0123"). Can be set at creation and edited later.
  • allowDuplicateHandles — whether the list allows the same handle to appear more than once (see Duplicate Handling). Fixed at creation.
  • isArchived — soft-delete flag (see Archiving).
  • entryCount, scannedCount — summary counts. Always returned on both the list and detail endpoints. scannedCount is the number of entries whose scannedAt is set.
  • createdAt, createdBy — when and by whom the list was created.

Lists are scoped to the combination of user + Metrc hostname + license number. You can only see and edit your own lists for the licenses you have access to.

Entry-level fields:

  • handle — the tag string itself (for example a Metrc package label).
  • name — optional freeform string identifier describing what the row is ("Vault shelf 3", "Top drawer"). Editable after creation.
  • metadata — a freeform JSON object (see Entry Metadata).
  • scannedAt — nullable timestamp (see The scannedAt Field).

Entry Metadata

Every entry carries a metadata object that accepts any JSON. Typical uses:

  • Display hints — a color or icon name so the UI can group entries visually.
  • Contextual notes — free text the scanner entered on-device.
  • Source tracking — where the handle came from ({"source": "manifest-M-0123"}), which makes it easy to filter later.
  • Workflow state — anything downstream tooling needs to remember between runs.

Metadata is opaque to the T3 API — it stores and returns whatever you put in. Treat it as a small per-entry scratchpad.

The scannedAt Field

scannedAt is the field that makes the inventory check-off workflow possible. It's nullable and starts out null. You flip it to a timestamp to mark an entry as scanned, and back to null to undo.

Updates go through the entry-update endpoint (PATCH /v2/tag-lists/{tag_list_id}/entries/{entry_id}). The same endpoint can update metadata and name in the same request.

Because scannedAt is just a timestamp, it doubles as an audit record: reading the list back tells you not only which tags were scanned, but exactly when.


Duplicate Handling

When a list is created, allowDuplicateHandles fixes its deduplication behavior:

  • false (default) — any entry whose handle already exists in the list (or is duplicated within a single create/append request) is silently skipped.
  • true — every entry is kept, including repeats of the same handle.

The right choice depends on what the handle represents. If a handle is a unique Metrc tag, leave duplicates off — scanning the same tag twice should be a no-op. If a handle is something that can legitimately appear multiple times (say, a SKU being counted), turn duplicates on.

This setting cannot be changed after the list is created; create a new list if you need to switch modes.


Archiving

Lists are not usually deleted — they're archived. Setting isArchived to true hides the list from the default list query but keeps every entry intact and still retrievable by ID. Setting it back to false restores normal visibility.

Archiving is a convenient way to close out a completed inventory run or shipment verification without losing the record.


Access and Pricing

Tag lists are scoped per user, per Metrc hostname, per license number. There is no cross-user visibility — two users on the same license see two independent sets of lists.

Reads are free. Fetching all lists and fetching a single list with its entries work on any Metrc login without a T3+ subscription.

Writes require T3+. Creating lists, appending entries, updating entries, archiving, and deleting all require a T3+ subscription.


Using Tag Lists via the T3 API

Every tag list endpoint is documented in the interactive API docs under the Tag Lists tag:

https://api.trackandtrace.tools/v2/docs/#/Tag%20Lists

The list collection endpoint (GET /v2/tag-lists) is paginated like other T3 collections — pageSize defaults to 100 and is capped at 500. By default it returns entryCount and scannedCount summaries without the actual entries. Pass ?includeEntries=true to embed every list's full entries inline; this is significantly more expensive for users with many or large lists, so prefer the default and fetch individual lists by ID when you need their entries.

A minimal Python example covering the end-to-end check-off flow with the bulk scan endpoint:

import requests

BASE = "https://api.trackandtrace.tools"
HEADERS = {"Authorization": f"Bearer {T3_JWT}"}

# 1. Create a named list, seeded with one handle.
create = requests.post(
    f"{BASE}/v2/tag-lists",
    headers=HEADERS,
    json={
        "licenseNumber": "LIC-00001",
        "name": "Manifest M-0123",
        "allowDuplicateHandles": False,
        "entries": [{"handle": "1A4..."}],
    },
).json()
tag_list_id = create["data"]["id"]

# 2. Append another handle.
requests.post(
    f"{BASE}/v2/tag-lists/{tag_list_id}/entries",
    headers=HEADERS,
    json={
        "licenseNumber": "LIC-00001",
        "entries": [{"handle": "1A5...", "metadata": {"source": "manifest-M-0123"}}],
    },
)

# 3. Bulk mark matching handles scanned in a single call.
#    `handlesNotFound` lists any supplied handles that weren't in the list.
result = requests.post(
    f"{BASE}/v2/tag-lists/{tag_list_id}/scan",
    headers=HEADERS,
    json={
        "licenseNumber": "LIC-00001",
        "handles": ["1A4...", "1A5...", "UNKNOWN..."],
    },
).json()
print(result["data"])  # {"scanned": 2, "scannedAt": "...", "handlesNotFound": ["UNKNOWN..."]}

Using Tag Lists in OpenTag

On mobile, tag lists are the OpenTag app's central feature. OpenTag creates and edits the same tag lists the T3 API exposes, so a list scanned on-device can be read back from a script, and vice versa. See the OpenTag page for the device workflow.


Next Steps

  • Read the OpenTag page for the mobile scanning workflow that drives tag lists.
  • Browse the T3 API documentation for how to integrate tag lists into scripts and automations.
  • Subscribe to T3+ to unlock tag list mutations and OpenTag's premium features.
  • Try the interactive API docs to explore every tag list endpoint.