io.tt SDK Docs
Guides

Supply Chain

Generating and Distributing Serialized Product Codes with the io.tt Supply Chain API

The Supply Chain module is the upstream counterpart to the consumer-facing experiences. It lets a brand mint unique, scannable identifiers (as plain URLs or GS1 Digital Links), group them under a programme, and attach them to a specific product (touchpoint). Once generated, each code resolves to a branded landing page that can host any io.tt experience — Prize Draw, Loyalty, Golden Ticket, Gifting, or Scan & Register.

Unlike the experience endpoints, the Supply Chain API is designed to be called server-side from your brand tooling (ERP, warehouse, label-generation pipeline) using a brand-scoped API key. The endpoints are exposed through a dedicated OpenAPI spec (tsoa-supply.json) and are consumed via the Speakeasy-generated Supply Chain SDK, not through the consumer @io-tt/sdk package covered by the Getting Started guide.

The API exposes six operations:

  • generateIds — enqueue a batch of codes
  • getJobStatus — poll the processing state of a batch
  • getItemsWithUrl — paginated list of generated codes (URL or SVG)
  • lookupProgramme — find a programme by ID or name
  • createProgramme — create a new programme for the brand
  • getProgrammes — paginated list of programmes

This guide walks through the full flow, the key concepts, and the rules the API enforces.


Core Concepts

Before calling the API, it helps to understand the four main entities:

EntityWhat it is
BrandThe entity owning the codes. Derived from your API key — you never pass it explicitly.
ProgrammeA campaign or activation. Every batch of codes belongs to exactly one programme. Referenced by programmeId or programmeName. If a name is given that doesn't exist, the API will create the programme automatically.
TouchpointThe physical product (SKU) the codes are applied to. Optional — if omitted, items are generated with no product linkage. Referenced by touchpointId or gtin.
Batch / Supply JobA batch is the set of items generated by one generateIds call. The supply job is the async handle you poll to check when the batch is ready.

How codes resolve

Each generated item has a short serial (the slug) printed under the QR/NFC. When scanned, the URL resolves one of two ways:

  • Plain redirecthttps://{domain}/{slug} — the default when no GS1 configuration is present.
  • GS1 Digital Linkhttps://{domain}/01/{gtin}/21/{slug} — used automatically when the touchpoint has a valid GTIN and GS1 domain configured. This is the GS1-compliant format used by standards-aware scanners and trade systems.

The domain defaults to your brand's configured domain, but can be overridden per batch.


Job Lifecycle

Code generation is asynchronous. A generateIds call returns immediately with a jobId; the batch is processed off a queue and moves through the following states:

PENDING → PROCESSING → ID_GENERATED → IMAGE_PENDING → COMPLETED

                                                      ERROR / FAILED

Only jobs in the COMPLETED state will return items from getItemsWithUrl. Any earlier state returns 404 with a message indicating the job is still pending, and terminal ERROR / FAILED states return 404 with the failure reason.


1. Authenticate

Every Supply Chain request uses a brand API key (distinct from the Experience API keys used for the consumer SDK). Keys are issued by your io.tt account manager and carry one or both of these scopes:

ScopeRequired for
read:supplygetJobStatus, getItemsWithUrl, lookupProgramme, getProgrammes
write:supplygenerateIds, createProgramme

Send the key as a bearer token:

Authorization: Bearer <your-supply-api-key>

2. Create or Look Up a Programme

Every batch must belong to a programme. You have three options:

  1. Look up by nameGET /v1/supply-chain/programme?programmeName=Spring 2026 Launch
  2. Look up by IDGET /v1/supply-chain/programme?programmeId=65f1a…
  3. Skip this step entirely and pass programmeName straight to generateIds — the API will create the programme on the fly if it doesn't exist.

To create a programme explicitly (with description, tags, date window, or markets):

POST /v1/supply-chain/programme
Content-Type: application/json

{
  "name": "Spring 2026 Launch",
  "description": "EMEA spring activation",
  "tags": ["spring", "emea"],
  "startDate": "2026-03-01",
  "endDate":   "2026-06-30",
  "marketIds": ["<marketId>"]
}

Response includes a dashboardUrl — a direct link to the programme in the io.tt dashboard.

To browse existing programmes, use the paginated list:

GET /v1/supply-chain/programmes?page=1&pageSize=20

3. Generate a Batch (generateIds)

The core operation. Submit the quantity you need, a programme reference, an optional touchpoint/GTIN, and a webhookUrl to be notified when processing completes.

POST /v1/supply-chain/generate-ids
Content-Type: application/json

{
  "quantity": 500,
  "programmeName": "Spring 2026 Launch",
  "gtin": "05012345678900",
  "url": "https://brand.example/landing",
  "webhookUrl": "https://brand.example/hooks/supply",
  "extra": {
    "poNumber": "PO-2026-0411",
    "printRun": "line-A"
  }
}

Request fields:

FieldTypeRequiredDescription
quantitynumberNumber of codes to generate.
programmeIdstringMongo ObjectId of an existing programme. Provide this or programmeName.
programmeNamestringProgramme name. If it doesn't exist, it will be created.
touchpointIdstringMongo ObjectId of the target touchpoint.
gtinstringGTIN of the target touchpoint. Validated as a GS1 Digital Link.
urlstringRedirect URL each code should resolve to.
domainstringOverride the domain codes resolve against. Defaults to your brand domain.
webhookUrlstringURL to POST the completion payload to.
extraobjectArbitrary client-supplied metadata, echoed back by getJobStatus. Useful for correlating jobs with your own IDs (PO numbers, print runs, etc).

Response (202 Accepted):

{
  "jobId": "65f1a2b3c4d5e6f7a8b9c0d1",
  "status": "pending"
}

Validation rules

  • Either programmeId (a valid Mongo ObjectId) or programmeName must be present.
  • gtin, if provided, must be a valid GS1 Digital Link identifier.
  • touchpointId or gtin must match a touchpoint owned by your brand (or be omitted entirely).

4. Wait for Completion

There are two ways to know when a batch is ready. Pick whichever fits your pipeline.

Option A — Webhook

When you provide a webhookUrl, the platform will POST a completion payload to it once the job reaches a terminal state:

{
  "jobId": "65f1a2b3c4d5e6f7a8b9c0d1",
  "status": "completed",
  "totalGenerated": 500,
  "timestamp": "2026-04-22T10:15:00Z"
}

On failure, status is "failed" and an error field carries the reason. Webhook delivery is best-effort and non-blocking — see the API-side webhooks documentation for details.

Option B — Polling

Call getJobStatus on an interval:

GET /v1/supply-chain/{jobId}/status

Response:

{
  "id": "65f1a2b3c4d5e6f7a8b9c0d1",
  "batchId": "65f1a2b3c4d5e6f7a8b9c0d2",
  "status": "COMPLETED",
  "createdAt": "2026-04-22T10:00:00Z",
  "updatedAt": "2026-04-22T10:15:00Z",
  "metadata": { "quantity": 500, "batchName": "SDK Batch - 2026-04-22T10:00:00Z" },
  "extra": { "poNumber": "PO-2026-0411", "printRun": "line-A" }
}

Typical batches complete in seconds to minutes depending on size.


5. Fetch the Generated Items (getItemsWithUrl)

Once the job is COMPLETED, page through the items. Choose the response format with responseType:

  • responseType=url — the raw redirect URL; use this if you render your own QR codes.
  • responseType=svg (default) — a link to a pre-rendered SVG QR code hosted by io.tt.
GET /v1/supply-chain/{jobId}/items?responseType=svg&page=1&pageSize=200

Response:

{
  "items": [
    {
      "id": "65f1…",
      "slug": "a3k7p",
      "brandId": "65ed…",
      "batchId": "65f1a2…",
      "url": "https://qr.io.tt/65ed…/65f1a2…/000000001-65f1….svg"
    }
  ],
  "total": 500,
  "page": 1,
  "pageSize": 200,
  "totalPages": 3,
  "dashboardUrl": "https://dashboard.io.tt/65ed…/links/65f1a2…"
}

Pagination is offset-based. pageSize is capped at 200 — larger values are silently clamped.


6. End-to-End Example

// Pseudo-code using a Speakeasy-generated Supply Chain SDK
const supply = new SupplyChainSDK({ apiKey: process.env.IOTT_SUPPLY_KEY });

// 1. Generate a batch
const { jobId } = await supply.generateIds({
  quantity: 500,
  programmeName: 'Spring 2026 Launch',
  gtin: '05012345678900',
  url: 'https://brand.example/landing',
  webhookUrl: 'https://brand.example/hooks/supply',
});

// 2. Poll until complete (or skip this and wait for the webhook)
let job;
do {
  await new Promise((r) => setTimeout(r, 2000));
  job = await supply.getJobStatus(jobId);
} while (job.status !== 'COMPLETED');

// 3. Page through the items
let page = 1;
while (true) {
  const res = await supply.getItemsWithUrl(jobId, 'svg', page, 200);
  for (const item of res.items) {
    await sendToPrintQueue(item.url);
  }
  if (page >= res.totalPages) break;
  page++;
}

7. Error Reference

HTTPErrorWhy
400ProgrammeMissingNameAndIdErrorNeither programmeId nor programmeName was provided.
400GtinIsNotValidThe supplied gtin failed GS1 Digital Link validation.
404ProgrammeNotFoundErrorprogrammeId did not match and no fallback programmeName was given.
404TouchpointNotFoundErrortouchpointId / gtin did not match any touchpoint owned by the brand.
404Job not found / still pending / erroredReturned from getItemsWithUrl and getJobStatus when the job is not COMPLETED.

All error responses share the shape:

{ "message": "…", "code": "…" }

8. Summary Table

MethodScopePurpose
generateIdswrite:supplyEnqueue a batch of codes and return a job handle.
getJobStatusread:supplyInspect the lifecycle state of a supply job.
getItemsWithUrlread:supplyPaginated list of items with URL or SVG links.
lookupProgrammeread:supplyResolve a programme by ID or name.
createProgrammewrite:supplyCreate a new programme for the brand.
getProgrammesread:supplyPaginated list of non-archived programmes.

Notes

  • Supply Chain is a brand-side workflow. Keep the API key server-side — never ship it to a consumer-facing client.
  • Every resolved code ultimately hands off to an io.tt experience. See the Experience Guides for how to build the consumer-facing side once codes are in circulation.
  • GTIN validation and GS1 Digital Link generation follow the GS1 Digital Link 1.x specification.
  • Programme and touchpoint lookups are always scoped to the authenticated brand — cross-tenant access is not possible.

On this page