TrueTag API

Version 1 · Pro plan

What it does

Create TrueTag units in bulk from your own software and read them back — serial number, verify URL, Shopify product, variant and SKU. Units live in TrueTag; nothing is written to Shopify. Units created through the API also appear in the TrueTag dashboard.

The API returns data only. It does not provide QR code images or label PDFs — see printing your own QR codes.

Getting a key

The API is part of the Pro plan. The store owner creates keys inside the TrueTag app in the Shopify admin, under API access. Each key is shown once, when it is created — store it somewhere safe. A store can have up to 5 active keys; revoke a key there at any time.

  • A live key (tt_live_…) creates real units. Every unit a live key creates counts toward the store's monthly unit allowance (10,000 a month on Pro) — the same allowance used by units created in the dashboard and by automatic serial numbers on orders.
  • A test key (tt_test_…) is free and unlimited: it returns realistic simulated data, never creates real units and never uses the allowance — see test mode.

Test first, then go live

Build and test your whole integration with a test key. Switch to a live key only when it runs in production.

When the store's monthly allowance is used up, live creates return 402 monthly_allowance_exceeded — and new orders stop getting serial numbers until the allowance resets on the 1st of the month (UTC). A test key lets you rehearse that error without using a single unit.

Authentication

Send the key in the Authorization header on every request:

Authorization: Bearer tt_live_…

The scheme must be exactly Bearer, followed by one space and the key.

Never put a key in a URL. Requests with a key in the query string are refused with 401 invalid_key. URLs end up in logs and browser history, so treat a key that was ever sent in a URL as leaked: revoke it in TrueTag and create a new one.

Create units

curl -X POST "https://truetag-rho.vercel.app/api/v1/units" \
  -H "Authorization: Bearer tt_test_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: factory-run-2026-09-22" \
  -d '{
    "quantity": 300,
    "shopify_product_id": "16285740761437",
    "shopify_variant_id": "51230000000000",
    "batch_lot": "Sept run"
  }'

quantity is required, a whole number from 1 to 1,000. shopify_product_id, shopify_variant_id and batch_lot (up to 100characters) are optional. A variant alone is enough — the product is taken from it; if you send both, the variant must belong to the product. TrueTag checks the IDs with Shopify before creating anything. Units get the product's warranty, or the store's default warranty.

Success is 201 with data.units and meta.count.

List units

curl "https://truetag-rho.vercel.app/api/v1/units?limit=100&batch_lot=Sept%20run" \
  -H "Authorization: Bearer tt_test_…"

curl "https://truetag-rho.vercel.app/api/v1/units?limit=100&cursor=<meta.next_cursor>" \
  -H "Authorization: Bearer tt_test_…"

Newest first. limit is 1–100 (default 50). Optional filters: batch_lot, shopify_product_id, shopify_variant_id and created_after (a date, e.g. 2026-09-01). To get the next page, send meta.next_cursor as cursor; stop when it is null.

Success is 200 with data.units — the same shape as a create.

Get one unit

curl "https://truetag-rho.vercel.app/api/v1/units/TT-46PKHVVA" \
  -H "Authorization: Bearer tt_test_…"

Looks up the exact serial in the key's store. Success is 200 with data.unit — one unit, not a list — including warranty. An unknown serial is 404 unit_not_found.

warranty.status is the status as stored. A warranty past its end date can still read active, so compare warranty.expires_atwith today's date yourself.

Unit fields

IDs are always strings. No customer data is ever returned.

{
  "id": "0b6f4c1e-2a7d-4f3b-9c8e-5d1a2b3c4d5e",
  "serial": "TT-46PKHVVA",
  "verify_url": "https://truetag-rho.vercel.app/v/<token>?s=<your-store>.myshopify.com",
  "status": "active",
  "shopify_product_id": "16285740761437",
  "shopify_variant_id": "51230000000000",
  "sku": "RF-JKT-BLK-L",
  "product_title": "Field Jacket",
  "batch_lot": "Sept run",
  "shopify_order_id": null,
  "created_at": "2026-09-22T14:18:31Z",
  "fulfilled_at": null
}
id
TrueTag unit ID (a UUID).
serial
Serial number, e.g. TT-46PKHVVA.
verify_url
The link to put inside the QR code. Use it exactly as returned.
status
active, or void if its order was cancelled or refunded. The database also allows refunded, which TrueTag does not currently set.
shopify_product_id
Shopify product ID, or null. TrueTag has no separate product ID — the TrueTag product ID is the Shopify product ID.
shopify_variant_id
Shopify variant ID, or null.
sku
The variant's SKU, recorded when a unit is created through the API with a variant; otherwise null.
product_title
Product name shown on the verify page, or null.
batch_lot
Your batch or lot label, or null.
shopify_order_id
Set only for units TrueTag created from a Shopify order; otherwise null.
created_at
When the unit was created (ISO 8601).
fulfilled_at
When its order was fulfilled (ISO 8601), or null.
warranty
Get one unit only: { status, expires_at }, or null when the unit has no warranty.

Test mode

A test key goes through the same checks as a live key — authentication, plan, rate limit and input validation, including the Shopify product and variant check — then answers with simulated units. It never creates real units and never uses your allowance. Successful test responses and validation errors carry meta.mode set to test; error responses from the guard — 401, 403, 429, and 503/500 — may not, because they are produced before or outside the test path. Use the key you sent, not meta.mode, to tell the two modes apart.

  • Create returns quantity simulated units with TEST- serials, echoing your product, variant and batch.
  • Create units are simulated and are not listed afterwards.
  • List pages through a fixed set of 250 sample units (TEST-SAMPL001 to TEST-SAMPL250), newest first. limit, cursor and the filters work exactly as they do for a live key.
  • Get returns that sample unit for a serial in the set, and a simulated unit for any other serial of the form TEST- followed by exactly 8 uppercase letters or digits (e.g. TEST-ABCD1234). Anything else is 404.
  • A test verify_urlopens a “Test code — not a real product” page. No scan is recorded.
  • Test requests have no quota beyond the per-key rate limit.

Simulating errors

Send the TrueTag-Test-Error header with a test key to rehearse the errors production can return. A live key ignores this header completely.

  • monthly_allowance_exceeded — create only. Answers 402 as a store with its allowance used up would, after the request is validated. Nothing is stored against your Idempotency-Key, and the header wins over a stored replay for that key.
  • rate_limited — every endpoint. Answers 429 with Retry-After: 1.
  • Any other value is 400 invalid_request.
curl -X POST "https://truetag-rho.vercel.app/api/v1/units" \
  -H "Authorization: Bearer tt_test_…" \
  -H "Content-Type: application/json" \
  -H "TrueTag-Test-Error: monthly_allowance_exceeded" \
  -d '{ "quantity": 10 }'

Responses and limits

Every response is JSON in the same envelope:

{ "success": true, "data": { … }, "error": null, "meta": { "mode": "live" } }
{ "success": false, "data": null, "error": { "code": "invalid_request", "message": "…", "field": "quantity" }, "meta": { … } }
  • 60 requests per minute per key.
  • Live-key creates count toward the store's monthly unit allowance; test-key requests never do.
  • 1–1,000 units per create request.
  • 1–100 units per list page.
  • 5 active keys per store.
  • On 429, 503 or 409 request_in_progress, wait the number of seconds in the Retry-After header, then retry.

Idempotency and retries

Send an Idempotency-Key header (1–255 visible ASCII characters) on every create, and reuse it when you retry the same request. Without it, a retried create makes a second batch.

  • Same key and same body: you get the first response again, with the header Idempotent-Replayed: true. No new units are created. The stored response expires after 24 hours — after that, the same Idempotency-Key starts a new batch.
  • Same key, different body: 409 idempotency_conflict.
  • A create that failed with an error response frees its key, so you can retry with the same key.
  • A request that never finished (for example, the connection dropped mid-way) can block that key for up to 24 hours with 409 request_in_progress. If that happens, first check whether the units already exist with GET /units?batch_lot=…. Only if they are absent, retry with a new Idempotency-Key — a stranded request may have created its units before it stopped.

Known limitation: rarely, a create that times out with 503 has in fact created its units. Before retrying such a create, check with GET /units?batch_lot=… — which is one more reason to give every batch its own batch_lot.

Errors

error.code is stable; error.message is plain English and may change.

Statuserror.codeMeaning
400invalid_requestSomething in the request is wrong; error.field names it.
401invalid_keyMissing, malformed or revoked key, a key in the URL, or the store uninstalled TrueTag.
402monthly_allowance_exceededThe request would go over the store's monthly unit allowance.
403pro_plan_requiredThe store is not on the Pro plan.
403api_not_enabledAPI access is not enabled for this store yet.
404unit_not_foundNo unit with that serial in this store.
409idempotency_conflictThe Idempotency-Key was used before with a different body.
409request_in_progressA request with this Idempotency-Key has not finished.
422product_not_foundNo product with that ID in this store's Shopify catalogue.
422variant_not_foundNo variant with that ID in this store's Shopify catalogue.
422variant_not_in_productThe variant belongs to a different product.
429rate_limitedToo many requests; wait Retry-After seconds.
503shopify_unavailableShopify could not be reached; nothing was created. Retry after Retry-After.
503temporarily_unavailableA temporary problem on our side. Retry after Retry-After.
500server_errorUnexpected; contact TrueTag support if it persists.

Printing your own QR codes

The API gives you the data; you make the codes. It does not return QR images or label PDFs.

  • Encode verify_url exactly as returned — never shorten it and never remove the ?s= part.
  • Keep the code square and at least about 2 cm wide, with a white quiet border around it.
  • Print a dark code on a light background.
  • Put your logo beside the code, never inside it.
  • Units created through the API can also be printed as ready-made labels from the TrueTag dashboard, free.