Skip to content

Scope Definition — Strengthening ID Identification

When an Amazon search input does not parse as an ASIN, the BFF must decide whether it is a product barcode identifier (UPC / EAN / ISBN) so it can route to identifier-mode SearchItems and exact-match the response against ItemInfo.ExternalIds. This document scopes four cumulative improvements to that identification step. They are additive: each builds on the previous one and raises precision without changing the downstream identifier-mode search that already exists (classifyIdentifierTokensbuildSearchRequestfilterByExternalIds).

The identifiers in play are the GTIN family Amazon actually exposes: UPC-A (GTIN-12), EAN-13 (GTIN-13), EAN-8 (GTIN-8), and ISBN-10 (books). Amazon returns these under ItemInfo.ExternalIds.{upcs,eans,isbns}; it does not expose GS1 logistics identifiers (GSIN, SSCC) or accept external-id lookup through GetItems (ASIN-only). See the UPC Fallback goal for the full API-capability findings.

ASIN vs identifier precedence (scope confirmation)

Section titled “ASIN vs identifier precedence (scope confirmation)”

Because extractAsin runs before classifyIdentifierTokens in the search dispatch (server/routes/amazon/search.ts), any token the ASIN parser claims never reaches identifier mode. The question for scope is whether a barcode identifier can be mis-claimed as an ASIN. The bare-ASIN test is “exactly 10 alphanumeric characters, containing at least one digit.”

IdentifierShape10 chars?Claimed by extractAsin?
UPC-A12 digitsnoNo — reaches identifier mode
EAN-1313 digitsnoNo — reaches identifier mode
EAN-88 digitsnoNo — reaches identifier mode
ISBN-10\d{9}[\dX]yesYes — routed to GetItems as an ASIN
  • UPC and EAN cannot collide. They are 8 / 12 / 13 characters; a single token cannot be both 10 chars and 12/13 chars. This is why the live probe’s 12-digit UPC correctly reached identifier mode. For the ticket’s target (UPC/EAN), there is no precedence conflict — a bare UPC/EAN bypasses ASIN parsing cleanly.
  • ISBN-10 does collide, and this is intentional: for print books Amazon’s ASIN is the ISBN-10, so GetItems([isbn10]) resolves the book directly. The lenient extractor’s regex (B[A-Za-z0-9]{9}|\d{9}[\dXx]) treats an ISBN-10 shape as an ASIN on purpose. ISBN-10 therefore stays on the ASIN path by design and is out of scope for the UPC/EAN identifier work.

Consequence for the checksum work (Improvement 2): the ISBN-10 branch of classifyIdentifierTokens is effectively unreachable for bare or pure-ISBN input (claimed as ASINs upstream); it fires only when an ISBN-10 is mixed with a non-ASIN identifier such as a UPC. Tests should not assert an ISBN-10-only identifier-mode path that cannot execute.

Improvement 1 — Structural classification (baseline)

Section titled “Improvement 1 — Structural classification (baseline)”

What. Classify a token purely by shape and length: 12 digits → UPC-A, 13 digits → EAN-13, 8 digits → EAN-8, 10 chars ending in a digit or X → ISBN-10. This is the behavior already implemented in server/lib/amazon/identifier-mode.ts (classifyIdentifierTokens).

Why. Zero-latency, no network round-trip, no external dependency. It is the gate that decides identifier mode versus keyword mode, so it must run first and cheaply.

How. Per-token regex match against the four patterns; a query qualifies for identifier mode only when every non-empty token matches. ISBN-10 trailing check character is upper-cased for canonical comparison.

Limitations (motivating Improvements 2–4).

  • Any 12-digit string classifies as a UPC-A, including transposed or mistyped numbers — a false positive spends a SearchItems call and returns noise that filterByExternalIds then discards as empty.
  • A 10-character all-digit token is ambiguous between ISBN-10 and an arbitrary number; shape alone cannot disambiguate.
  • UPC-A and EAN-13 are the same GTIN in different widths (a UPC-A is an EAN-13 with a leading zero), but structural classification treats them as unrelated buckets.

What. Before accepting a token as an identifier, validate its GS1 / ISBN check digit: mod-10 (GS1 weighted 3-1) for UPC-A, EAN-13, and EAN-8; mod-11 for ISBN-10 (weights 10..1, remainder 10X).

Why. The check digit is the identifier’s built-in typo detector. Validating it rejects mistyped or random numbers before they reach Amazon, which:

  • eliminates wasted SearchItems calls on non-identifiers (latency + quota),
  • disambiguates the 10-digit ISBN-10 case (a real ISBN passes mod-11; a random 10-digit number almost never does),
  • makes “input classified as UPC but returns nothing” a meaningful signal (genuine not-on-Amazon) rather than noise from a bad number.

How. Add pure validator functions alongside the existing patterns in identifier-mode.ts; a token is an identifier only if it matches a shape and its check digit is valid. Fully unit-testable with published example barcodes.

Tradeoffs. Slightly stricter — a genuinely malformed-but-intended barcode (e.g. a scan glitch) is rejected and falls through to keyword search. That is the correct behavior (searching a broken barcode as keywords is harmless), but it should be noted so support cases are understood.

What. Treat UPC-A, EAN-13, and EAN-8 as one GTIN value space: normalize each validated token to a canonical GTIN-14 (left-pad with zeros), and match the response’s external IDs in that same normalized space rather than by exact string equality per bucket.

Why. The same physical product is frequently represented as a 12-digit UPC in one place and a 13-digit EAN (leading-zero UPC) in another. Amazon may store the value under either upcs or eans. Normalizing both the query identifier and the response identifiers to GTIN-14 means a UPC query matches an EAN-stored product (and vice versa), closing a class of false-empty results that exact per-bucket string matching in filterByExternalIds misses today.

How.

  • Normalize the input identifier to GTIN-14 after validation (Improvement 2).
  • In the response filter, normalize each upcs/eans display value to GTIN-14 before comparing; keep ISBN matching separate (ISBNs are not GTIN-14 and have their own check scheme, though ISBN-13 is a GTIN — out of scope here).
  • Preserve the existing dedupe-by-ASIN and order semantics of filterByExternalIds.

Tradeoffs. Adds a normalization step on both sides of the match and a small amount of GTIN logic. Highest-value of the three for real-world catalog data, but only meaningful once Improvement 2 guarantees the tokens are valid GTINs.

The scope above was confirmed against the live Amazon Creators API (dev credentials, US marketplace, associate tag arda06-20, credential version 3.1) using a standalone probe that mirrors creators-client.ts. To avoid an ambiguous empty result, the probe bootstrapped a real identifier: it ran a keyword search, read a UPC from a returned product’s ItemInfo.ExternalIds, then searched by that UPC.

Method. Target product: Duracell Coppertop AA (24 ct), ASIN B0035LCFNQ, UPC 041333270357. Query: SearchItems(keywords="041333270357", SearchIndex="All").

Results.

  • Searching by the UPC returned 5 items (totalResultCount = 5); the target ASIN was among them, and 3 of the 5 carried the exact UPC — so filterByExternalIds retains ≥1 match. The 2 relevance-only items had no external IDs and are correctly dropped by the filter.
  • Every item exposed the same GTIN in both buckets, differing only by a leading zero: upcs: ["041333270357"] and eans: ["0041333270357"].
  • A single UPC mapped to multiple ASINs (repackaged / resold variants); the filter’s dedupe-by-ASIN returns each distinct ASIN.

What it confirms for the scope.

  • Identifier-mode search by UPC already works end-to-end today — so this document’s improvements are about precision and coverage of identification, not enabling a missing capability.
  • Improvement 3 is empirically justified, not theoretical: because the same value lives under both upcs (12-digit) and eans (13-digit, leading-zero) buckets, today’s exact per-bucket string match in filterByExternalIds would miss a product that stores the value only as an EAN when the query is a UPC (or vice versa). GTIN-14 normalization on both sides closes that gap.
  • The one-UPC-to-many-ASINs behavior means “found” is a set, not a single hit — the filter’s dedupe/order semantics must be preserved by any change.

Keyword-mode probe (fallback viability). A second probe searched the same product’s UPC and EAN as plain SearchItems keywords in default mode (no SearchIndex=All), and also as a mixed “barcode + words” query:

  • Bare UPC, default keyword mode → target product returned.
  • Bare EAN (13-digit), default keyword mode → target product returned.
  • Mixed “UPC + words”, default keyword mode → target product returned.

This confirms the keyword fallback is a real safety net, not a dead end — Amazon’s full-text index resolves a barcode number as a keyword. It also confirms mixed input needs no barcode extraction: keyword search over the whole string already returns the product. The distinction that keeps identifier mode as the primary for bare barcodes: keyword results are a relevance list (variety packs, adjacent products), whereas identifier mode + filterByExternalIds returns the exact barcode match. Caveat: verified on one product; catalog-wide keyword-on-barcode reliability is a post-PR verification item, which is precisely why identifier mode leads and keyword is the fallback.

The probe scripts are retained under the project’s scratch area for re-running against other identifiers (e.g. an EAN-only product to further stress Improvement 3).

Improvement 4 — Input normalization (separators)

Section titled “Improvement 4 — Input normalization (separators)”

What. Before classification, strip formatting from a candidate token: internal spaces, hyphens, and other non-digit separators (e.g. 0 41333 27035 7, 978-0-13-...). Keep a trailing ISBN-10 X.

Why. Real pasted and scanned barcodes carry human/rendering separators. The current classifier splits on whitespace and matches ^\d{12}$, so a spaced or hyphenated barcode is mis-split or rejected outright. Normalization is low-effort and high-payoff — arguably the highest-value add after the check digit.

How. A pure normalization step feeding classifyIdentifierTokens; it must not merge genuinely separate tokens in a multi-identifier query (normalize within a candidate, not across the whole string indiscriminately).

Robust disambiguation of length-ambiguous codes

Section titled “Robust disambiguation of length-ambiguous codes”

Customers use a wide range of scanners with different, unknowable configurations. The identification step therefore makes no assumption about symbology or scanner behavior. Instead it treats a normalized digit string as potentially several identifier types, keeps every interpretation whose check digit validates, and searches what survives — Amazon’s exact GTIN match makes the final call, with keyword search as the backstop.

Enumeration by length:

LengthInterpretations tried
8EAN-8 (GTIN-8) and UPC-E expanded to UPC-A(12) — both
11UPC-A after padding a leading zero
12UPC-A
13EAN-13 (covers ISBN-13 978/979 and leading-zero UPC-A)
14GTIN-14
10ISBN-10 — handled upstream by ASIN precedence, not here

Ladder:

  1. Normalize (Improvement 4) → raw digit string.
  2. Enumerate the interpretations above; keep only those whose check digit validates (Improvement 2). This prunes noise so enumerating many candidates does not spray garbage at Amazon.
  3. Normalize each survivor to canonical GTIN-14 (Improvement 3).
  4. Search the surviving set (one OR query; see dependency below) and filter with the GTIN-normalized filterByExternalIds. Amazon’s exact match keeps whichever representation it actually stores.
  5. If no interpretation validates, or the identifier search returns nothing, degrade to the keyword fallback over the original input.
  6. Still nothing → empty result.

Why this is robust to any scanner. A raw UPC-E (8 digits), a scanner-expanded UPC-A (12 digits), and a genuine EAN-8 are all covered by different rungs of the enumeration; a genuinely ambiguous 8-digit code is searched both ways and the exact-match filter keeps the real one; and a checksum-failing scan still degrades to keyword search rather than dead-ending.

Dependency — |-as-OR. Step 4’s single OR query relies on | behaving as a true union in Amazon Keywords. The existing code already pipe-joins multi-identifier queries in production, but this has not been independently verified. If post-implementation verification shows it is not a true OR, the fallback is one call per surviving interpretation (≤ 2 for the 8-digit case), merged and deduped by ASIN — a bounded cost incurred only in the rare ambiguous case. Implementation builds on the OR path; verifying | = true OR is on the verification checklist.

Apply the improvements as one ordered gate in the identification step:

  1. Normalize (Improvement 4) strips separators from each candidate token.
  2. Enumerate + shape (Improvement 1) lists every identifier a normalized token could be, including the length-ambiguous 8-digit EAN-8 / UPC-E case.
  3. Check digit (Improvement 2) keeps only interpretations that validate, pruning noise and disambiguating borderline cases; when none validate the token falls through to keyword search.
  4. GTIN normalization (Improvement 3) canonicalizes surviving identifiers and the response external IDs to GTIN-14 so UPC/EAN representations match across buckets.

Downstream is unchanged: validated, normalized identifiers flow into the existing identifier-mode SearchItems request and filterByExternalIds response filter. No GetItems changes are involved (it remains ASIN-only). See “Robust disambiguation of length-ambiguous codes” above for the full enumerate-checksum-OR-backstop ladder that makes this robust to any scanner.

Placement. The barcode primitives (check-digit validation, UPC-E→UPC-A expansion, GTIN-14 normalization, separator stripping, length classification) are generic — not Amazon-specific — so they live in a new src/lib/shared/scanning/ module, reusable by non-Amazon searches. The Amazon-coupled orchestration (identifier-mode.ts classification wiring and the filterByExternalIds response filter) stays in src/server/lib/amazon/ and imports the primitives from scanning, mirroring how existing Amazon code imports pure asin.ts from src/lib/shared/amazon/.

  • ISBN-13 / GTIN unification for books (ISBN matching stays exact for now).
  • Any GetItems-side external-id lookup (unsupported by the API).
  • The separate “ASIN parsed but GetItems returned zero products” fallback branch — tracked in the UPC Fallback goal; this document covers only the identification of non-ASIN inputs.

Copyright: (c) Arda Systems 2025-2026, All rights reserved