API Reference

REST API Documentation

Integrate TraceLayer into your own systems. All API endpoints are JSON-based and authenticated via session cookie or API key.

Authentication

All authenticated endpoints require a valid session. Authentication is handled by your configured auth provider (Logto, Supabase, Auth0, etc.).

Sign-in flow:

  • GET /api/auth/sign-in — redirects to your auth provider
  • GET /api/auth/callback — handles the OAuth callback, sets a session cookie
  • GET /api/auth/sign-out — clears the session

The session cookie tl_session is a signed JWT valid for 7 days. All subsequent API calls use this cookie automatically.

Access levels:

  • public — no auth required (QR resolve endpoint)
  • staff+ — staff or admin role required
  • admin — admin role required

Base URL

https://qr.yourdomain.com

TraceLayer is deployed on your own infrastructure with your own domain. The API base URL is the same as your QR domain — no separate API subdomain needed.

QR Codes

POST/api/qrstaff+

Bulk generate QR code tokens

Request Body
{ "count": 50, "entityType": "ingredient_lot" }
Response
{ "tokens": ["DCC5UQ5H", "K9N3RT7W", ...] }
GET/api/qrstaff+

List QR codes with optional filters

Query Params
?status=unused&limit=100&offset=0
Response
{ "items": [{ "token": "DCC5UQ5H", "status": "unused", "entityType": null, "createdAt": "..." }] }
GET/api/qr/:tokenpublic

Resolve a QR token — logs a scan event. Returns entity data based on caller role.

Response
{ "token": "DCC5UQ5H", "entity": { "id": "...", "name": "...", "metadata": {...} } }
GET/api/qr/:token?format=pngstaff+

Download QR code image. Use format=svg for SVG output.

Response
Binary PNG or SVG image

Entities

POST/api/entitiesstaff+

Create a new tracked entity (ingredient lot, product, batch, etc.)

Request Body
{
  "name": "Dark Chocolate Couverture 70%",
  "type": "ingredient_lot",
  "state": "registered",
  "visibility": "internal",
  "metadata": {
    "lotNumber": "L2026-0421",
    "supplier": "Valrhona",
    "photoUrls": ["/api/uploads/tenant/uuid.jpg"]
  }
}
Response
{ "id": "ent_abc123", "name": "...", "type": "ingredient_lot", ... }
GET/api/entities/:idstaff+

Get a single entity by ID.

Response
{ "id": "...", "name": "...", "state": "in_use", "metadata": {...} }
PATCH/api/entities/:idstaff+

Update entity name, type, state, visibility, or metadata. Logs a state_changed event if state changes.

Request Body
{ "state": "completed", "metadata": { "notes": "batch finished" } }

Batches

POST/api/batchesstaff+

Create a production batch.

Request Body
{
  "batchCode": "B-2026-047",
  "entityId": "ent_abc123",
  "recipeVersion": "v2.1",
  "notes": "Summer blend"
}
GET/api/batchesstaff+

List batches. Filter by state or entity ID.

Query Params
?state=in_progress&entityId=ent_abc123
PATCH/api/batches/:idstaff+

Update batch — mark complete, add notes, set expiry.

Request Body
{ "state": "completed", "expiresAt": "2026-09-01T00:00:00Z" }
POST/api/batches/:id/ingredientsstaff+

Add an ingredient lot to an existing batch. Logs added_to_batch event.

Request Body
{ "entityId": "ent_xyz789", "quantity": "2.4", "unit": "kg" }

Events

GET/api/eventsstaff+

List events. Filter by QR token, entity ID, event type, or date range.

Query Params
?token=DCC5UQ5H&type=registered&limit=200
Response
{
  "events": [{
    "id": "evt_123",
    "eventType": "registered",
    "userId": "user_abc",
    "userRole": "staff",
    "createdAt": "2026-06-14T09:15:00Z",
    "metadata": { "lotNumber": "L2026-0421" }
  }]
}

Uploads

POST/api/uploadstaff+

Upload a photo. Send as multipart/form-data with a "file" field.

Request Body
multipart/form-data: file=<image>
Response
{ "url": "/api/uploads/tenant-id/uuid.jpg" }

Event Types

All event types that can appear in the event log. The log is append-only — events cannot be deleted.

qr_scannedregisteredlinked_to_entitystate_changedadded_to_batchremoved_from_batchorder_createdorder_fulfilledmarked_voidviewed_public_pageintake_completedshippedreturned

Error Responses

All error responses use standard HTTP status codes and return JSON:

// 4xx / 5xx response body
{
  "error": "Human-readable error message",
  "code":  "machine_readable_code"   // optional
}
400
Bad Request
401
Unauthorised
403
Forbidden
404
Not Found
409
Conflict
422
Validation Error
429
Rate Limited
500
Server Error

Ready to integrate?

Start a trial and get API access on the Growth plan or higher.