Docs · Guide

A million serialized codes from one CSV

Once the GS1 licence is in place, this is an afternoon rather than a sprint. You import a CSV of products, not of units; the per-unit codes are derived from each product in runs of up to 100,000 and stream back as a file. The licence is the slow part, and it is a prerequisite with no shortcut: publishing under a GS1 key needs a GS1 Company Prefix licensed from a GS1 Member Organisation, which takes days to weeks. Everything below assumes you have one or are waiting on one.

The prerequisite that decides your timeline

A Digital Link asserts that the identifier printed inside it was licensed to the publisher by GS1. No resolver standard checks that, so this platform does: you register the GS1 Company Prefix you licensed, and every publishing action checks the key against your prefixes first. That check runs on every origin, the shared GS1 host and your own custom domain alike. Owning a domain is not permission to print somebody else's identifier on it.

  • Licensing happens at GS1, not here · a prefix comes from a GS1 Member Organisation, on their timetable, and days to weeks is the honest range. Nothing on this platform accelerates it, and no page here should imply otherwise.
  • Registering it is dashboard-only, on purpose · adding a licence is a legal declaration, stored with the user, IP and timestamp. It is deliberately not exposed to API keys or to MCP agents, because a credential has no person behind it to be accountable for the declaration. There is no endpoint for it and there is not going to be one.
  • Attested is not verified, and we say which · a licence you declare is attested. With a GS1 registry connection configured it can be verified against the registry instead. The status is shown next to every item either way.
  • There is no certification to hold · GS1 does not certify resolvers or publishers, so "GS1 certified" describes nothing that exists, ours included. A licence is a right to use identifiers, not a badge.
  • Some prefixes are refused outright · store-internal ranges, coupons, refund receipts and Bookland ISBNs identify nothing outside one retailer, and they are the most common self-serve mistake in this category.

If the prefix is not there yet, the rest of this page is a plan rather than an afternoon. That is worth knowing before a production date is committed, which is why it is the first section and not a footnote.

The CSV is your product list, not your unit list

This is the step that decides whether the job is small. A million-row import would be a million rows to validate, bill, store and re-import every time a URL changes. The catalogue import is instead one row per SKU: the product master that every serial beneath it inherits.

keyAi,keyValue,name,defaultUrl,brand,17
01,09506000134352,Chilli Oil 250ml,https://yourbrand.example/chilli-oil-250,Northgate,261231
01,09506000134369,Chilli Oil 500ml,https://yourbrand.example/chilli-oil-500,Northgate,261231
  • Required columns are four · keyValue, name and defaultUrl, with keyAi defaulting to 01 for a GTIN. Optional columns cover description, brand, sku, gpc, net content, target market, image URL and status.
  • A numeric column is an application identifier · a column headed 17 is the expiry date, 3103 is net weight. Each value is validated against its published GS1 format before it is stored, and encoded into the printed URI's query string.
  • Dry run first · the import accepts a dry-run flag that validates every row, charges nothing, and returns what committing the file would cost. Use it. The rows it rejects are the ones you would otherwise pay to create wrongly.
  • Rows are independent, and reported by line · one bad check digit in row 700 does not roll back the other 4,999. Errors come back with their line number and a reason.
  • It upserts on the key · re-importing a corrected file fixes the catalogue instead of duplicating it. Creating an item costs 200 credits; updating one is free, because it was paid for when it was created and charging again would bill you 200 credits to fix a typo.
  • 5,000 rows per call · a larger catalogue is several calls, not a bigger file. A wallet that runs dry mid-file fails only the rows it cannot pay for; the rows above it are created and paid for, and the failures name themselves.
  • Every destination is safety-screened · per row, before the item is written. A rejected destination is a failed row with a reason, not a silent pass.

Serials are derived, not stored, and never imported

Once an item exists, you ask for a count. Nothing is written per unit: each serial is computed from the batch's secret seed, so a run of 100,000 codes writes exactly one row in our database. A serial is 18 characters, all of them inside the GS1 application-identifier character set, and all three parts do work:

  • Four characters of batch prefix · which job issued it, so verification knows where to look without scanning every batch you ever ran.
  • Six characters of index · Crockford base32, which drops I, L, O and U so a human reading a code off a pack cannot mistype it into a different valid serial. That is roughly 1.07 billion units per job.
  • Eight characters of truncated HMAC · 40 bits of authenticator over the job and the index, under the batch seed. This is what makes a serial unguessable rather than merely reproducible: someone who collects real serials off real packs still cannot mint one that verifies.
serial,url,compressedUrl,elementString

# one row, unwrapped:
serial          7F2A00001AK3VP7QZM
url             https://dl.yourbrand.example/01/09506000134352/10/LOT4471/21/7F2A00001AK3VP7QZM
compressedUrl   the same identifier packed through GS1's optimisation table, and emitted only
                when decompressing it again reproduces the plain URI exactly
elementString   (01)09506000134352(10)LOT4471(21)7F2A00001AK3VP7QZM

The compressed URI and the element string ride alongside the plain one because those are what a label printer and a scanner-verification tool actually consume. The file is streamed rather than assembled in memory, so a 100,000-row run starts arriving immediately instead of timing out.

  • The caps, plainly · 100,000 serials per call over the REST API, so a million units is ten calls. 10,000 per call over MCP, because an agent should confirm a non-refundable spend with a human at that size. 500 on a server-rendered print sheet, which draws every symbol.
  • The job id is the batch · it comes back in the X-Job-Id response header on every issuance, and the REST API adds X-Credits-Charged so a client can reconcile the spend without guessing from the rate card. Keep it.
  • Re-deriving that job is free and byte-identical · the same serials, whether you want them as a print sheet this week and wanted them as a CSV last week, or you simply lost the file. Issuing charges; re-deriving issues nothing, so charging for it would bill twice for one set of codes.
  • The key has to have a serial qualifier · a GTIN does. An SSCC or a GIAI does not, because those already identify one instance, and asking to serialize them is refused with a reason rather than quietly producing something unprintable.
  • The lot is validated before a single credit moves · a batch value with a space in it is not in the GS1 character set, and unvalidated it would produce a hundred thousand printed codes that the resolver answers with 400 for the rest of their lives. Every check that can fail runs before the debit, for the same reason.
  • Verification is a recomputation, not a lookup · which is why the answer never depends on a table we have to keep growing, and why it stays fast at a million units. It deliberately is not an event trail: it says a serial was issued, not where it has been.

What one of those million scans actually does

A scan arrives as a full Digital Link path: key, then lot, then serial. Resolution matches the most specific link registered and walks up rather than dead-ending · serial, then lot, then the key, which always carries a default link because a linkset without one is rejected at write time. So a warranty page can be pinned to one unit and a recall notice to one lot, without a page per unit existing anywhere.

The mechanism worth understanding before you print is what the serialized scan lands on. A linkset entry can point at a URL you own, or it can be bound to a page we host, and binding is not the same as pasting our URL into the box:

1 creditA bound page is rendered by the resolver in process, so the scan is one scan. Pasting a URL to one of our own pages instead costs two: the resolver bills the redirect and the page bills the arrival.
Your hostThe page is served on whatever host the code was printed against. A pasted URL is written against one origin, so a code on your own domain would bounce the scanner onto ours.
Lot and serialThe page can read them from the code that was scanned. A redirect cannot: they are path segments, and a 302 carries only the query string. This is exactly why a serialized run resolved through a plain redirect arrives at a page that has forgotten which unit was scanned.

Before the press run, put a photograph of one printed sample through the free decoder and read the path back out. A serial that survives the symbol and the ink is the only proof that the batch you are about to multiply by a million is the batch you meant.

What a million units costs

200 creditsper catalogue item, once · the product master every serial inherits
0 creditsyour first catalogue item · your first one is granted, once per workspace
1 creditper serialized unit · the ordinary dynamic-code rate, not a serialization surcharge
1 creditper scan, like every other scan on the platform
0 creditsre-deriving a batch, verifying a serial, exporting, editing the linkset, carriers

Do the arithmetic out loud, because the issuance half is the half people budget for and the scan half is the one that grows. Issuing 1,000,000 units spends 1,000,000 credits at 1 credit each, plus 200 credits for each SKU's product master. The Volume pack at $799 carries 1,300,000 credits, so it covers that run and roughly 300,000 scans of it. A run whose units are all scanned more than once needs more credits than the run cost to make.

Credits are prepaid and valid 18 months, and any top-up rolls the whole balance forward another 18. There is no per-code annual fee to re-budget and nothing renews on its own.

That is the mechanism and the arithmetic. The buying case for serialization · the comparison against other Digital Link vendors, and what commonly goes wrong at their redirect · lives on serialized QR codes at scale.

What to know before you commit a press run

The honest limits, including the ones that are gaps rather than decisions. A printed code cannot be recalled, so these belong before the order, not after it.

  • An exhausted wallet does not pause a Digital Link today · ordinary dynamic codes pause after their grace period, 14 days past a zero balance, and serve a paused page. GS1 resolution has no such gate in the path: it keeps resolving. We would rather write that down than have you find the inconsistency yourself, and it is a gap we expect to close rather than a feature.
  • Routing rules are browser-only · issuance and the catalogue are fully scriptable, but the rules graph behind a code is not reachable from the API today. Plan for a person to set those up once, before the run.
  • No print grading · you get the symbol geometry, the millimetre size at the target X-dimension, and the four-module quiet zone measured by test. You do not get an ISO 15415 grade for a printed sample, which is a scanner-verifier's job.
  • A serialized linear symbol is a GS1-128 · no retail symbology has room for a serial number, so the auto-pick sends every serialized run there. If a pack needs an EAN-13 as well, that is the dual-mark artwork, not the serialized one.
  • No event trail · verification answers "did we issue this", by recomputation. Where a unit has been is an EPCIS question and this is not an EPCIS system.
  • The identifier is the irreversible part · where a code points stays editable forever and costs nothing to change. The key, the lot and the serial are printed into the pattern. Check them on a real sample first.

Questions

Do I import one CSV row per unit?

No, and this is the part most people have backwards. The file you import is your product list, one row per SKU, capped at 5,000 rows per call. Per-unit codes are derived from a product afterwards and arrive as a CSV you download, so a million units is an output file, never an input one.

How long does a million-unit run actually take to set up?

The licence is the slow part and everything else is fast. A GS1 Company Prefix is licensed from a GS1 Member Organisation and takes days to weeks. Once it is registered, importing the catalogue and issuing serials is an afternoon: 100,000 serials per API call, so a million units is ten calls, each streamed straight to a file.

What happens if I lose the CSV of a million serials?

Keep the job id that came back in the X-Job-Id header and the batch re-derives byte for byte, free. Serials are computed from the job seed rather than stored, so nothing was lost that cannot be recomputed, and re-deriving issues nothing and charges nothing.

What does a serialized run of a million units cost?

The product master for the SKU is 200 credits, once, and your first one is granted, once per workspace. Each serialized unit is 1 credit, the ordinary dynamic-code rate. Then every scan is 1 credit, and over the life of a printed run that half grows while the issuance half does not, so budget the scans out loud rather than the units alone.

Can a script or an agent do the whole pipeline?

The catalogue import, the serial issuance and the verify call all work over the REST API, and issuance also works over MCP at up to 10,000 serials per call. Registering the GS1 Company Prefix does not and will not: it is a legal attestation that has to record a real human, so it is dashboard-only by design. Routing rules are browser-only too, so plan for a person to set those up.

If a serial fails verification, is the product counterfeit?

Not on its own. Verification recomputes the serial’s HMAC under its batch seed and answers whether this workspace ever issued it, in constant time. A false means it was not issued by you, which covers a typo, a smudge and a wrong workspace as well as a fake. It is the first thing worth looking at, not a verdict, and it is never a location history: this answers whether a unit was issued, not where it has been.