Support Tickets
- Support Tickets
- What a Ticket Is
- Filing a Ticket
- Attaching Files
- Attachment Limits
- Reading Your Tickets
- Ticket Lifecycle
- Limits and Behavior
- Next Steps
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¶
- Authentication — getting a secret key or a JWT
- OpenAPI Spec — the full machine-readable definition