The payload grammar.
Fixed vs variable termination, GS separators, AIM symbology prologues, date and decimal AI semantics — a pinned table, not folklore.
One label's worth of data, blown out.
rendered as a GS1-128 barcode — in the stripes, digits ride in pairs (one symbol per two digits); the label text above is the human-readable form of the same payload
GTIN 0950600013435 · check 2
sum 0×3 + 9×1 + 5×3 + 0×1 + 6×3 + 0×1 + 0×3 + 0×1 + 1×3 + 3×1 + 4×3 + 3×1 + 5×3 = 78
check (10 − 78 mod 10) mod 10 = 2 → mod10 passThe row.
| scheme | gs1.element-string |
|---|---|
| grammar | An AI is a 2–4 digit tag followed by a typed value. Fixed-length AIs run straight into the next AI; variable-length AIs terminate at GS (0x1D) or end of payload. The tag/length/type table is pinned data derived from the GS1 General Specifications. |
| check digit | Structural — carried values bring their own rails (AI 01 carries the GTIN mod-10). Errors are typed with byte offsets. |
| canonical form | A typed AI map. generate emits deterministic AI ordering; resolve accepts any valid ordering. |
| symbologies | GS1-128 (]C1) · GS1 DataMatrix (]d2) · GS1 QR (]Q3) · and the wider AIM prologue set (]C0 ]E0 ]I1 ]A0 ]Q1 ]L2) — the prologue names the carrier, never the identifier |
| enrichment | — (grammar layer; enrichment attaches to the carried keys) |
| join rule | The AI map is the event's raw material: 01+21 → epcList instance, 01+10 → quantityList class, 00 → parentID. |
| pinned digest | gs1.element-string sha256:6e4b22877ad1c3add56c885785f5768670f9c6310f15150dc69d3aff6294dac4 |
| as data | /schemes/ai.json |
In practice.
The grammar is exactly why one parser covers both 2D payload forms: a GS1 element string and a Digital Link URI normalize to the same typed AI map, and the round-trip law — resolve(generate(x)) === x — is asserted over that map, not over bytes that include symbology framing.
On the blog.
-
GS1 Application Identifiers: the grammar inside the symbol
AIs are the typed field system of the physical world — (01) GTIN, (10) lot, (17) expiry, (21) serial, (00) SSCC — and an FNC1-delimited element string is a binary wire format with parsing rules, not "text in a barcode." Parsed here byte by byte, from the AIM prefix to the last variable-length field.
-
What did I just scan? Payload identification from the first three bytes
A scanner hands you bytes, not meaning. The AIM symbology identifier, the FNC1 position, Code-39 framing, and URI shape classify any payload deterministically — worked here on ten raw payloads, the way resolve does it, from a pinned decision table rather than folklore.
-
Element string or Digital Link URI: the encoding decision Sunrise leaves to you
A 2D symbol can carry the same GTIN+lot+expiry as an FNC1 element string or as a GS1 Digital Link URI. One resolves in a consumer's phone, one does not — and the choice belongs to the GTIN owner, not to the Sunrise 2027 program. The decision matrix, worked with generate.
-
resolve(generate(x)) === x: generation and resolution are one function, tested
The write side and the read side of an identifier layer must be exact inverses — every generated element string, Digital Link URI, and rendered symbol must resolve back to the identical canonical identity — and that law is a CI gate over a published golden corpus, not a design preference. The harness, the corpus split, and how to check the claim yourself.
Get an API key.
The local verbs run from npx barcoding.dev today with no key. A key opens the network half of the verb set.