01 = GTIN, 10 = batch/lot, 21 =
serial, 00 = SSCC, 8003 = GRAI, and so on. This skill teaches an
agent how to take an element string off a barcode scan and unpack it
into structured data the rest of the system can query.
When to use
- A scanner / OCR / EDI feed produced a raw element string and you need structured fields (GTIN, lot, expiry, serial, SSCC, GRAI, GIAI).
- The agent received a GS1 Digital Link (
https://id.gs1.org/01/...) and needs to extract the AIs the path encodes. - A user pasted a GS1-128 barcode payload and asked “what does this mean?”.
build-gs1-digital-link.
The two encodings
| Encoding | Example | When you see it |
|---|---|---|
| Bracketed (human readable) | (01)00614141000005(10)A123(21)SN9999 | EDI documents, marketing copy, debug output. |
| Unbracketed (machine readable) | 010061414100000510A123\x1d21SN9999 | Actual barcode payload (GS1-128, DataMatrix, QR with GS1 framing). |
\x1d, also written
<GS> or ASCII 29) to mark the end of a variable-length value.
Step 1 — Detect the variant
(01)09506000134352(17)270101(10)ABC123→ bracketed010950600013435217270101\x1d10ABC123→ unbracketedhttps://id.gs1.org/01/09506000134352/10/ABC123→ Digital Link path
Step 2 — Know the fixed-length AIs
These AIs have a predefined length; the parser jumps that many characters and continues. No separator needed.| AI | Length (after AI) | Field | Notes |
|---|---|---|---|
00 | 18 | SSCC | Serial Shipping Container Code. |
01 | 14 | GTIN | Always zero-padded to GTIN-14 internally. |
02 | 14 | GTIN of contained trade items | For logistics labels. |
11 | 6 | Production date (YYMMDD) | |
12 | 6 | Due date (YYMMDD) | |
13 | 6 | Packaging date (YYMMDD) | |
15 | 6 | Best-before date (YYMMDD) | |
16 | 6 | Sell-by date (YYMMDD) | |
17 | 6 | Expiration date (YYMMDD) | Day 00 means “end of month”. |
20 | 2 | Variant number | |
8005 | 6 | Price per unit of measure | |
8006 | 18 | GCTIN (component) |
Step 3 — Know the variable-length AIs
These accept up to a maximum number of characters and must be terminated by FNC1 (\x1d) when followed by another AI. If the
variable-length value is the last element in the string, no separator
is needed.
| AI | Max length | Field |
|---|---|---|
10 | 20 | Batch / lot number |
21 | 20 | Serial number |
22 | 20 | Consumer product variant |
240 | 30 | Additional product identification |
241 | 30 | Customer part number |
242 | 6 | Made-to-order variation |
30 | 8 | Variable count |
37 | 8 | Count of trade items |
400 | 30 | Customer purchase order |
401 | 30 | Consignment number (GINC) |
402 | 17 | Shipment identification (GSIN) |
403 | 30 | Routing code |
410–417 | 13 | Trading-partner location codes |
420 | 20 | Ship-to postal code (single postal authority) |
421 | 12 | Ship-to postal code + ISO country code |
8003 | up to 30 | GRAI (Global Returnable Asset Identifier) |
8004 | up to 30 | GIAI (Global Individual Asset Identifier) |
8011 | 12 | Component / part serial |
8020 | 25 | Payment slip reference |
90–99 | 30 | Internal applications |
Step 4 — Parse, bracketed → structured
Step 5 — Parse, unbracketed → structured
This is where most agents trip up. The algorithm:- Read 2-4 leading digits and try them against the AI table. Try the
longest prefix that matches a known AI (so
8003is preferred over80+03). - If the AI is fixed-length, slice
lengthcharacters as the value. - If it’s variable-length, slice up to the next
\x1d(or end of string, if there’s no separator). - Repeat from the position after the value.
- Normalize bracketed input by stripping brackets and re-emitting an unbracketed string with FNC1 separators inserted, then run the unbracketed algorithm. One code path is easier to maintain than two.
- Validate the Mod-10 check digit on AI
01(GTIN). The Closientapps.core.types.GTIN.from_raw()does this — reuse if you’re in Python. - Accept the literal characters
^or~as FNC1 sentinels (some scanners emit those instead of\x1d).
Step 6 — Parse, GS1 Digital Link → structured
Digital Link path is path-based AI encoding:/{AI}/{value}/{AI}/{value}/…
apps.products.gs1_digital_link.parse_gs1_digital_link)
returns a typed dataclass with gtin, lot_number, expiry_date, and
serial_number already broken out — call that directly if you’re
running inside the monorepo.
Step 7 — Validate
Beyond the AI table, two checks matter:- GTIN check digit (AI 01). Mod-10; the last digit is computed from the first 13. Reject silently-malformed scans rather than continuing with a corrupt GTIN.
- Date plausibility (AI 17 in particular).
YYMMDDwithMMin 01-12,DDin 00-31 (where00is sentinel for “last day of month”). The Closient scanner emits structuredfreshness_metafor AI 17 — seeapps.scanner.services.gs1_dates.
Example end-to-end
Input (off a meat-case label):01→ fixed 14 →09506000134352(GTIN).17→ fixed 6 →300901(expiry2030-09-01).10→ variable, up to FNC1 →45454GH(lot).21→ variable, no separator left →SN0001(serial).
Common mistakes
- Forgetting FNC1 after a variable-length AI before another AI
starts — produces a corrupted parse where
10ABC12321SN0001is read as a 20-character lot ending in21SN0001. - Not zero-padding GTIN-8 or GTIN-12 to GTIN-14 before lookups — Closient stores GTINs as GTIN-14 internally; pad with leading zeros.
- Treating
(01)from a Digital Link query string as the SSCC AI 00 because the parser was eager about 2-digit prefixes; always try the longest AI prefix first. - Trusting
YYas 4-digit year. GS1 saysYYin 00-49 → 20YY, 50-99 → 19YY (sliding window). Don’t hardcode the century.
Related skills
build-gs1-digital-link— inverse: given GTIN + AIs, emit a valid Digital Link URI.resolve-gtin— fetch product detail once you have the GTIN.check-product-availability— find local stock for a GTIN.capture-epcis-event— once you have GTIN + lot + serial, record a supply-chain event.closient-api-quickstart— authenticate before calling any of the above.
Authoritative references
- GS1 General Specifications (v25 or later) — section 3 (AIs) and section 7 (data carriers).
- GS1 Digital Link Standard 1.4.
- ISO/IEC 15418 — GS1 Application Identifiers.
- Closient internal:
backend/apps/products/gs1_digital_link.py(parser) andbackend/apps/core/types/_gs1.py(typed value objects).