Skip to content

Support Tickets


Support Tickets

The /v2/support/tickets endpoints let you report a bug or issue to T3 support from your own code, and check back on what you reported.

A T3+ subscription is not required. Anyone who can authenticate can file a ticket.

What a Ticket Is

A ticket is a bug report with your account attached to it. It carries a title and a description, optionally the page you were on and the version of the client you were using, an arbitrary bag of context you choose, and up to five files.

Tickets are owned by the Metrc account, not by an individual credential. Any secret key or JWT for the same username and hostname sees the same tickets. Nobody else's tickets are ever visible to you.

Filing a Ticket

With no attachments, POST a JSON body:

import requests

SECRET_KEY = "your-secret-key"

response = requests.post(
    "https://api.trackandtrace.tools/v2/support/tickets",
    headers={"X-T3-API-Key": SECRET_KEY},
    json={
        "title": "Scanning a package tag opens the wrong package",
        "body": (
            "Scanning tags on the Active Packages page opens the package "
            "above the one I scanned. Started this morning, happens every "
            "time, on two machines."
        ),
        "contactEmail": "[email protected]",
        "pageUrl": "https://ca.metrc.com/industry/C11-0000001-LIC/packages",
        "licenseNumber": "C11-0000001-LIC",
        "clientVersion": "3.2.1",
        "stateContext": {
            "route": "/packages/active",
        },
    },
)

ticket = response.json()["data"]
print(ticket["publicId"])   # keep this to check back on the ticket
print(ticket["status"])     # OPEN

title, body and contactEmail are required. Everything else is optional, but pageUrl and licenseNumber are the two most useful things you can include — they are usually the difference between a bug we can reproduce and one we cannot.

contactEmail is how we reach you

Your Metrc username identifies the account, but it is not a mailbox. Without an address there is no way for us to ask a follow-up question — the only thing you would ever see is resolutionNote, and only if you thought to read the ticket again.

It must be a single, well-formed address; a list is rejected. It is checked before the ticket is written, so a typo comes back as a 400 rather than as a reply you never receive. It is returned on your own tickets and is never visible to anyone else.

licenseNumber is context, not a permission check

It is stored exactly as you send it and is not validated against the licenses your account can reach. That is deliberate: "I cannot see license X" is a legitimate ticket, and validating this field would reject exactly that report.

stateContext is yours to shape. Nothing in it is validated: put the active route, the last few console errors, whichever bits of client state you think will help. The only rule is that it must be a JSON object that serializes to at most 64 KB.

Attaching Files

To attach screenshots or logs, send multipart/form-data instead. The same JSON object goes in a part named payload, and each file goes in a part named file:

import json
import requests

payload = {
    "title": "Scanning a package tag opens the wrong package",
    "body": "Happens every time, on two machines.",
    "contactEmail": "[email protected]",
    "pageUrl": "https://ca.metrc.com/industry/C11-0000001-LIC/packages",
    "clientVersion": "3.2.1",
}

with open("screenshot.png", "rb") as screenshot, open("console.log", "rb") as log:
    response = requests.post(
        "https://api.trackandtrace.tools/v2/support/tickets",
        headers={"X-T3-API-Key": SECRET_KEY},
        data={"payload": json.dumps(payload)},
        files=[
            ("file", ("screenshot.png", screenshot, "image/png")),
            ("file", ("console.log", log, "text/plain")),
        ],
    )

ticket = response.json()["data"]
for attachment in ticket["attachments"]:
    print(attachment["filename"], attachment["contentType"], attachment["byteSize"])

Attachments cannot be downloaded again

Attachments are stored privately for T3 support. Reading a ticket back tells you a file's name, type and size — there is no URL and no download endpoint. Keep your own copy of anything you might need.

Attachment Limits

Limit Value
Files per ticket 5
Size per file 10 MB
Total size per ticket 25 MB
Accepted types PNG, JPEG, GIF, WEBP, PDF, plain text, CSV, JSON

Two things about how types are checked:

The file's contents decide, not its name or its declared type. Renaming archive.zip to screenshot.png and declaring it as image/png still gets it rejected, because the bytes are inspected. The exception is plain text, CSV and JSON, which have no signature to inspect and are taken at their declared type.

Attachments are validated before anything is stored. If one file is rejected, the whole request fails and no ticket is created — you will not end up with a half-filed ticket missing its screenshot. The error names the offending file:

{
  "title": "Attachment Rejected",
  "status": 400,
  "detail": "'screenshot.png' is a application/zip file, which attachments do not accept. Attach an image, a PDF, or a text, CSV or JSON file.",
  "code": "SUPPORT_TICKET_ATTACHMENT_REJECTED"
}

Reading Your Tickets

List everything you have filed, newest first:

response = requests.get(
    "https://api.trackandtrace.tools/v2/support/tickets",
    headers={"X-T3-API-Key": SECRET_KEY},
    params={"status": "OPEN", "pageSize": 25},
)

for ticket in response.json()["data"]:
    print(ticket["status"], ticket["publicId"], ticket["title"])

status accepts OPEN or CLOSED. The response is paginated with the usual page, pageSize, total and totalPages fields.

Read one ticket by its publicId:

response = requests.get(
    f"https://api.trackandtrace.tools/v2/support/tickets/{public_id}",
    headers={"X-T3-API-Key": SECRET_KEY},
)

ticket = response.json()["data"]
print(ticket["status"])
print(ticket["resolutionNote"])

A publicId belonging to a different Metrc account returns 404, the same as one that does not exist at all.

Ticket Lifecycle

Every ticket is created OPEN. T3 support works it and closes it.

There is no endpoint that changes a ticket — no edit, no close, no delete. This is deliberate: a ticket is a record of what you reported at the time you reported it.

The reply comes back through resolutionNote, which is null until someone has something to say and is filled in when the ticket is worked. Reading the ticket is the whole feedback channel, so poll it, or check it when you notice status has become CLOSED.

ticket = requests.get(
    f"https://api.trackandtrace.tools/v2/support/tickets/{public_id}",
    headers={"X-T3-API-Key": SECRET_KEY},
).json()["data"]

if ticket["status"] == "CLOSED":
    print(ticket["resolutionNote"])
    # "Fixed in extension 3.2.4. Update from the Chrome Web Store."

Limits and Behavior

Behavior Detail
Subscription Not required
Ownership The Metrc account (username + hostname)
Open tickets per account 25. Closing one restores the allowance.
title 1–200 characters, required
body 1–20,000 characters, required
contactEmail A single address, up to 254 characters, required and validated
pageUrl Up to 2,000 characters, optional
licenseNumber Up to 100 characters, optional, not validated
clientVersion Up to 50 characters, optional
stateContext Any JSON object, up to 64 KB serialized
Unknown fields Rejected — a misspelled field is an error, not silently ignored

Reaching the open ticket limit returns 409 with code SUPPORT_TICKET_LIMIT_EXCEEDED. It exists to stop a retry loop from filling the queue; if you genuinely have that many open issues, get in touch.

Next Steps