Skip to content

Search


The /v2/search endpoint provides a single text-search entry point across the most commonly searched Metrc collections. Hand it a tag, a manifest number, an item name, or a partial label, and it returns a ranked list of every matching object across the relevant Metrc collections — without you having to know which collection to look in.

A T3+ subscription is not required to use this endpoint.

What /search Does

Each request goes through five steps:

  1. Format detection. The query string is classified into one or more formats: DATETIME, HEX_STRING, INTEGER, or BASIC_STRING. A query can match multiple formats — for example, "12345" is both INTEGER and HEX_STRING.
  2. Endpoint selection. If endpointIds is omitted, the 17 default-enabled endpoints are used. If endpointIds is provided, exactly that set is used (including any endpoints that are off by default).
  3. Format gating. Endpoints whose searchable fields don't accept any of the detected query formats are removed from the request and reported in skippedMetrcEndpointIds. For example, "Blue Dream" (BASIC_STRING) skips transfer endpoints, which only search manifestNumber.
  4. Parallel fan-out. The remaining endpoints are queried in parallel against Metrc. Each endpoint is asked for at most pageSize matches (default 10, max 100).
  5. Score and sort. Each matched object is scored using the query length, the matched field's length, and the position of the match within that field. Results from all endpoints are merged into a single list and sorted by score descending.

Query Formats

Format detection determines which endpoints are eligible. The detection is greedy — a single query can satisfy multiple formats simultaneously, in which case it unlocks the union of their endpoints.

Format Detection rule Example
DATETIME ISO 8601 date or datetime: ^\d{4}-\d{2}-\d{2}(T...)?$ 2024-07-17
HEX_STRING Starts with 1A (Metrc tag prefix) or is all hex characters (0-9a-fA-F) 1A4FF0100000022000000123
INTEGER All digits 12345
BASIC_STRING Anything that doesn't match a more specific format above Blue Dream

Note

No endpoint currently searches date fields. A pure DATETIME query (e.g. 2024-07-17) returns an empty data array with every targeted endpoint reported in skippedMetrcEndpointIds. Use a tag, label, manifest number, or name instead.

Endpoint Catalog

The 26 endpoints /search can query, what each one searches, the formats it accepts, and whether it's queried by default.

Endpoint ID Searches Accepted formats Default
AVAILABLE_TAGS label HEX_STRING, INTEGER, BASIC_STRING
USED_TAGS label HEX_STRING, INTEGER, BASIC_STRING
VOIDED_TAGS label HEX_STRING, INTEGER, BASIC_STRING
ACTIVE_ITEMS name (also scores strainName) BASIC_STRING
ACTIVE_LOCATIONS name BASIC_STRING
ACTIVE_STRAINS name BASIC_STRING
ACTIVE_HARVESTS name BASIC_STRING
INACTIVE_HARVESTS name BASIC_STRING
ACTIVE_PLANTBATCHES name BASIC_STRING
INACTIVE_PLANTBATCHES name BASIC_STRING
ACTIVE_PACKAGES label HEX_STRING, INTEGER, BASIC_STRING
INTRANSIT_PACKAGES label HEX_STRING, INTEGER, BASIC_STRING
INACTIVE_PACKAGES label HEX_STRING, INTEGER, BASIC_STRING
TRANSFERRED_PACKAGES packageLabel HEX_STRING, INTEGER, BASIC_STRING
VEGETATIVE_PLANTS label HEX_STRING, INTEGER, BASIC_STRING
FLOWERING_PLANTS label HEX_STRING, INTEGER, BASIC_STRING
INACTIVE_PLANTS label HEX_STRING, INTEGER, BASIC_STRING
ACTIVE_MOTHER_PLANTS label HEX_STRING, INTEGER, BASIC_STRING
INACTIVE_MOTHER_PLANTS label HEX_STRING, INTEGER, BASIC_STRING
INCOMING_ACTIVE_TRANSFERS manifestNumber INTEGER, BASIC_STRING
INCOMING_INACTIVE_TRANSFERS manifestNumber INTEGER, BASIC_STRING
OUTGOING_ACTIVE_TRANSFERS manifestNumber INTEGER, BASIC_STRING
OUTGOING_INACTIVE_TRANSFERS manifestNumber INTEGER, BASIC_STRING
OUTGOING_REJECTED_TRANSFERS manifestNumber INTEGER, BASIC_STRING
ACTIVE_SALES receiptNumber INTEGER, BASIC_STRING
INACTIVE_SALES receiptNumber INTEGER, BASIC_STRING

To opt into off-by-default endpoints (INACTIVE_*, VOIDED_TAGS), pass them explicitly via endpointIds. Providing endpointIds overrides the default set entirely — if you want to add off-by-default endpoints to the defaults, you have to list every endpoint you want.

Validation Rules

/search rejects requests that can't produce useful results, with a 400 Bad Request. The most common rejections:

  • Empty query (or whitespace-only) — there's no fan-out target. Provide a real query string.
  • Pure ISO 8601 date/datetime query (e.g. 2024-07-17, 2024-07-17T20:26:07) — no endpoint searches date fields, so the request would silently return empty. Use a tag, label, manifest number, or name instead.
  • Unknown endpointIds — the error message names the specific unrecognized IDs (not the whole list you sent).
  • Out-of-range pageSize — must be an integer in [1, 100].
  • Out-of-range minScore — must be a number in [0.0, 1.0].

Response Shape

Every successful response has the same seven top-level keys.

{
  "data": [
    {
      "endpointId": "ACTIVE_PACKAGES",
      "score": 0.7142857142857143,
      "matchedEntry": { "...": "..." }
    },
    {
      "endpointId": "TRANSFERRED_PACKAGES",
      "score": 0.0833333333333333,
      "matchedEntry": { "...": "..." }
    }
  ],
  "queriedMetrcEndpointIds": [
    "ACTIVE_PACKAGES",
    "INTRANSIT_PACKAGES",
    "TRANSFERRED_PACKAGES",
    "AVAILABLE_TAGS"
  ],
  "failedMetrcEndpointIds": [],
  "skippedMetrcEndpointIds": [
    "ACTIVE_ITEMS",
    "ACTIVE_LOCATIONS"
  ],
  "detectedQueryFormats": ["HEX_STRING", "INTEGER"],
  "endpointMetadata": [
    { "endpointId": "ACTIVE_PACKAGES", "truncated": true, "totalAvailable": 47 },
    { "endpointId": "INTRANSIT_PACKAGES", "truncated": false, "totalAvailable": 2 },
    { "endpointId": "TRANSFERRED_PACKAGES", "truncated": false, "totalAvailable": 1 },
    { "endpointId": "AVAILABLE_TAGS", "truncated": false, "totalAvailable": 0 }
  ],
  "request": {
    "query": "1A4FF...",
    "licenseNumber": "LIC-00001",
    "pageSize": 10,
    "minScore": 0.0
  }
}

data is sorted by score descending. The shape of each matchedEntry depends on its endpointId — a match from ACTIVE_PACKAGES looks like a Metrc package, a match from INCOMING_ACTIVE_TRANSFERS looks like a Metrc transfer. Always check endpointId before using a result.

queriedMetrcEndpointIds vs failedMetrcEndpointIds vs skippedMetrcEndpointIds

Field Meaning
queriedMetrcEndpointIds Endpoints that were called against Metrc and returned successfully. Their results are in data.
failedMetrcEndpointIds Endpoints that were called but errored — Metrc API error, timeout, or insufficient license permissions. Their results are missing.
skippedMetrcEndpointIds Endpoints that were not called because the detected query format isn't compatible with any of their fields. No Metrc traffic was generated.

Together, the three arrays account for every endpoint the request would have touched. Use detectedQueryFormats if you need to understand why an endpoint was skipped.

endpointMetadata and the truncated Flag

Each successful endpoint contributes one entry to endpointMetadata, naming itself, whether its response was truncated, and Metrc's reported total count.

Field Meaning
endpointId The Metrc endpoint this entry describes — one of the IDs in queriedMetrcEndpointIds.
truncated true when Metrc had more matches than pageSize returned. Raise pageSize to retrieve them.
totalAvailable Metrc's reported total count for the underlying query — the size of the unbounded result set.

Failed and skipped endpoints are omitted (no Metrc response to derive from).

A typical flow: receive the response, find any endpointMetadata entry with truncated: true, and decide whether to re-issue the search with a larger pageSize or scope it down with endpointIds.

request Echo

The request object echoes the resolved request parameters — values after server-side defaults and trimming. Useful for:

  • Debugging proxied or cached responses where the original URL is no longer obvious.
  • Clients that fan out multiple concurrent searches and need to match responses to requests.
  • Confirming that a default value (e.g. pageSize: 10, minScore: 0.0) was actually applied as expected.

endpointIds isn't echoed — the resolved set is queriedMetrcEndpointIds plus skippedMetrcEndpointIds.

Tuning Result Volume with pageSize

The pageSize parameter controls how many matches each downstream Metrc endpoint may return. It defaults to 10 and is capped at 100. The total number of items in data is at most len(queriedMetrcEndpointIds) * pageSize.

Use a larger pageSize when the default isn't enough — for example, when a license has many similarly-named items and the top 10 from each endpoint isn't covering the matches you're looking for. Use a smaller pageSize when the request is purely a "did anything match" check.

curl 'https://api.trackandtrace.tools/v2/search?licenseNumber=LIC-00001&query=Blue%20Dream&pageSize=50' \
  -H 'Authorization: Bearer <TOKEN>'

pageSize applies uniformly to every queried endpoint. Out-of-range values (< 1 or > 100) return 400 Bad Request.

If you're not sure whether a higher pageSize would yield more results, look at endpointMetadata in the response: any entry with truncated: true had more matches available than your pageSize returned, and totalAvailable reports Metrc's full count.

Filtering Noise with minScore

The minScore query parameter drops results whose relevance score is below the threshold, server-side. Scores are in [0, 1], where 1.0 is an exact full-string match. Defaults to 0.0 (no filtering).

curl 'https://api.trackandtrace.tools/v2/search?licenseNumber=LIC-00001&query=Blue%20Dream&minScore=0.1' \
  -H 'Authorization: Bearer <TOKEN>'

Useful when:

  • A short or common query string produces a long tail of low-score matches that the UI just discards anyway.
  • You're looking for "is there a strong match?" and want the server to filter out the dross before serializing it.

The filter is per-result and applied before sorting. In SSE mode, result events still fire even when every match was filtered out — the event's results array will simply be empty. That tells the client the endpoint completed without truncation lying about availability.

Streaming Responses (SSE)

For interactive UIs that want to render results as soon as each Metrc endpoint replies — instead of waiting for the slowest one — /search can stream Server-Sent Events. There are two equivalent ways to opt in:

  • Add ?stream=true to the URL (easy to test from curl, easy to document).
  • Send Accept: text/event-stream (what EventSource does natively).

The response is a text/event-stream body containing two kinds of events:

  • event: result — emitted once per completed downstream endpoint, in completion order (fastest first). Payload contains endpointId, status (success or failed), an error string (failures only), and a results array sorted by score descending.
  • event: done — emitted once after every downstream endpoint has finished. Payload mirrors the JSON-mode summary: queriedMetrcEndpointIds, failedMetrcEndpointIds, skippedMetrcEndpointIds, and detectedQueryFormats.

Sample stream:

event: result
data: {"endpointId":"AVAILABLE_TAGS","status":"success","results":[{"score":1.0,"matchedEntry":{"...":"..."}}],"truncated":false,"totalAvailable":1}

event: result
data: {"endpointId":"ACTIVE_PACKAGES","status":"success","results":[{"score":0.71,"matchedEntry":{"...":"..."}}],"truncated":true,"totalAvailable":47}

event: result
data: {"endpointId":"INTRANSIT_PACKAGES","status":"failed","error":"Metrc Connection Failed","results":[]}

event: done
data: {"queriedMetrcEndpointIds":["AVAILABLE_TAGS","ACTIVE_PACKAGES"],"failedMetrcEndpointIds":["INTRANSIT_PACKAGES"],"skippedMetrcEndpointIds":["ACTIVE_ITEMS"],"detectedQueryFormats":["HEX_STRING","INTEGER"],"request":{"query":"1A4FF...","licenseNumber":"LIC-00001","pageSize":10,"minScore":0.0}}

Browser usage with EventSource:

const url = `/v2/search?licenseNumber=LIC-00001&query=${encodeURIComponent('Blue Dream')}`;
const source = new EventSource(url, { withCredentials: true });

source.addEventListener('result', (e) => {
  const { endpointId, status, results } = JSON.parse(e.data);
  // Render incrementally
});

source.addEventListener('done', (e) => {
  source.close();
});

curl usage:

curl -N 'https://api.trackandtrace.tools/v2/search?licenseNumber=LIC-00001&query=Blue%20Dream&stream=true' \
  -H 'Authorization: Bearer <TOKEN>'

(-N disables curl's output buffering so events appear as they arrive.)

Note

The SSE stream emits results in completion order, not score order. Each event's own results array is locally sorted by score, but the global ranking that JSON mode produces requires the client to merge events as they arrive.

Limits and Behavior

  • Per-endpoint cap of pageSize matches. Each downstream endpoint returns at most pageSize results (default 10, max 100). The total number of items in data is at most len(queriedMetrcEndpointIds) * pageSize. Watch endpointMetadata[].truncated to know when raising pageSize would yield more.
  • Per-endpoint timeout (~10s). Each downstream Metrc call is bounded so a single slow endpoint can't dominate total response time. Endpoints that exceed the timeout land in failedMetrcEndpointIds with an error of "Metrc Request Timeout". In SSE mode the corresponding result event still fires — failed endpoints don't block the rest of the stream.
  • Latency varies by endpoint. Tag, item, location, and strain searches typically return in well under a second. Package, plant, and especially transfer searches can take several seconds. JSON-mode total request time is bounded by the per-endpoint timeout; SSE-mode delivers fast endpoints' results first.
  • Narrow scope for speed. If you know the kind of object you're after, pass endpointIds to drop everything else. A query against endpointIds=AVAILABLE_TAGS,USED_TAGS will be much faster than the default fan-out.
  • Per-license. Like every collection endpoint, /search is scoped to the licenseNumber query parameter. Searching across multiple licenses requires multiple requests.
  • Free. No T3+ subscription required.

Examples

Substitute your API token (Authorization: Bearer ...) and license number into each example.

Tag scan — find every collection a tag appears in:

curl 'https://api.trackandtrace.tools/v2/search?licenseNumber=LIC-00001&query=1A4FF0100000022000000123' \
  -H 'Authorization: Bearer <TOKEN>'

Manifest number lookup — find a transfer by its manifest number:

curl 'https://api.trackandtrace.tools/v2/search?licenseNumber=LIC-00001&query=1234566' \
  -H 'Authorization: Bearer <TOKEN>'

This query is detected as HEX_STRING and INTEGER, so it searches every endpoint that accepts either — packages, plants, tags, transfers, and so on.

Item or strain name search — narrowed to items and strains for speed:

curl 'https://api.trackandtrace.tools/v2/search?licenseNumber=LIC-00001&query=Blue%20Dream&endpointIds=ACTIVE_ITEMS,ACTIVE_STRAINS' \
  -H 'Authorization: Bearer <TOKEN>'

Include inactive packages in a label search:

curl 'https://api.trackandtrace.tools/v2/search?licenseNumber=LIC-00001&query=0000123&endpointIds=ACTIVE_PACKAGES,INTRANSIT_PACKAGES,INACTIVE_PACKAGES' \
  -H 'Authorization: Bearer <TOKEN>'

Next Steps

  • Browse the full OpenAPI Spec for the formal request/response schema.
  • See API Scripts for examples of building higher-level workflows on top of /search.