Skip to content

UPC Fallback for Amazon Search

Extend the Amazon product search in arda-frontend-app so that when an input does not resolve to an ASIN (bare ASIN or Amazon product URL), it is treated as a product barcode identifier (UPC / EAN) and searched against Amazon by that identifier, instead of returning nothing. This raises the hit rate for scans and pasted barcodes where the shopper has a UPC/EAN rather than an ASIN.

The identifier-mode search this relies on already exists and works end-to-end — confirmed against the live Amazon Creators API (see the scope definition, “Live API verification”). This project therefore closes the remaining gap rather than building a new capability: hardening how a non-ASIN input is identified as a barcode (structural → check-digit → GTIN normalization, per the scope definition).

Interpretation of “ASIN is not found” (confirmed with product owner): the in-scope reading is input does not resolve to an ASIN → try it as a UPC/EAN. The alternative reading — input parses to a valid ASIN but GetItems returns no product — is out of scope: that branch has no barcode in the input to fall back to.

RepositoryRolePlanned Changes
arda-frontend-appPrimaryHarden identifier detection (identifier-mode.ts), add the keyword fallback rung, and add filterByExternalIds GTIN normalization — all in the /search path; unit tests and mock handler coverage.
documentationSupportingThis project’s artifacts under reference-data/item/add-items-from-amazon/upc-fallback; advance status frontmatter through its lifecycle.
  1. A bare barcode input (valid UPC/EAN, not an ASIN) is searched in identifier mode and returns the exact-match product(s) via filterByExternalIds — precise, not a relevance list.
  2. When identifier matching yields no exact match, the search degrades to the plain keyword result over the same input; only when that also yields nothing is an empty result returned — never an error. (Mixed “barcode + words” input goes straight to keyword mode, unchanged — see the fallback ladder.)
  3. Identifier detection validates the check digit (mod-10 for UPC/EAN, mod-11 for ISBN-10) and normalizes UPC/EAN to a common GTIN space, so a UPC query matches a product Amazon stores only as an EAN (and vice versa).
  4. All matching ASINs for an identifier are returned (deduped by ASIN), matching keyword-search “show all” behavior — no collapsing to a single “best” hit.
  5. The SPA renders all matches for a UPC/EAN query via the existing /search results list — no new import UI. /import is unchanged.
  6. Existing dispatch paths (single/multi-ASIN GetItems, keyword SearchItems, recognised rejections, ISBN-10-as-ASIN precedence) are unaffected.
  7. Unit tests cover identification (valid/invalid checksum, separator/spacing, GTIN normalization, EAN-8/UPC-E), the keyword-fallback rung, and the show-all result set; npm run lint and npx tsc --noEmit pass.

The Amazon search route (src/app/api/amazon/search/route.ts) delegates to searchAmazon in src/server/routes/amazon/search.ts. That module dispatches in stages:

  1. ASIN extract-and-dispatch — a strict extractAsin on the query; on success it calls fetchCreatorsApiItems([asin]) (GetItems) and maps the result. This is where an unresolved ASIN currently returns an empty list (search.ts ~lines 104–135) — the target insertion point for the fallback.
  2. Multi-token ASIN — batch GetItems when every token is a valid ASIN.
  3. Identifier / keyword SearchItems — relaxation-based search for everything else, including bare identifier tokens (UPC is already an identifier mode here via classifyIdentifierTokens).

A bare UPC/EAN already bypasses ASIN parsing (lengths 8/12/13 ≠ 10) and reaches identifier mode today; ISBN-10 is a deliberate exception that stays on the ASIN path (Amazon’s book ASIN is the ISBN-10). See the scope definition, “ASIN vs identifier precedence”, for the full analysis.

The dispatch is an ordered ladder; each rung is tried only when the previous one does not apply or yields nothing. This makes the identifier→keyword degradation explicit rather than an emergent gap.

  1. ASIN / URL — input resolves to an ASIN (bare or Amazon product URL) → GetItems([asin]).
  2. Identifier (bare barcode)every token is a valid UPC/EAN (structural
    • check digit) → identifier-mode SearchItems(SearchIndex=All) + filterByExternalIds (GTIN-normalized). This returns exact barcode matches, not a relevance list.
  3. Keyword fallback — when rung 2 yields zero exact matches, degrade to the plain keyword result over the same input, with no external-id filter. Unconditional: a barcode that matched no product still degrades to keyword rather than dead-ending. (Verified: Amazon keyword search does return a product by its UPC/EAN — see the scope definition’s Live API verification.)
  4. Terminal — when rung 3 also yields zero, return an empty result (never an error). Behavior for genuinely-unknown inputs is unchanged.

Mixed input (barcode + descriptive words) is not a barcode by the rung-2 “every token” test, so it goes straight to keyword mode — its current behavior, confirmed to return the product. No barcode-extraction-from-mixed is performed (deliberate simplification: the extra words signal the user wants a broader search, and precision matters most for a bare scan).

Implementation note (cost). Rung 3 for a bare barcode is free: rung 2 already fetched SearchItems(keywords=id, SearchIndex=All), so skipping filterByExternalIds on that same response yields the keyword result with no extra API call. No separate fresh keyword call is needed, because mixed input never enters rung 2 in the first place.

  • Identifier detection hardening (per scope definition): separator normalization → structural enumeration → check-digit validation → GTIN-14 normalization, including robust EAN-8 / UPC-E disambiguation that makes no assumption about scanner behavior (enumerate both, checksum-prune, OR-search, keyword backstop) and acceptance of 11-digit (leading-zero) UPC.
  • Keyword fallback rung — when identifier matching yields no exact match, degrade to the keyword result over the same input (reuse-and-skip-filter; no extra API call). Unconditional; terminal state is an empty result.
  • filterByExternalIds normalization — match query identifiers against the response in a common GTIN space so UPC/EAN representations cross buckets.
  • Unit tests and MSW mock handler coverage for the above.

All of the above lands in the /search path only. The SPA search panel (useAmazonSearchPanel) already calls searchAmazon and renders a results list for any non-ASIN query, so “show all” for a UPC requires no SPA UI change — it follows automatically once /search routes UPC/EAN through identifier mode.

Mixed “barcode + words” input keeps its current keyword-mode behavior (no barcode extraction) — see the fallback ladder above.

  • /import (the Chrome extension endpoint, PDEV-652) — unchanged. Its only consumer is the extension, which imports from an Amazon product page where an ASIN/URL is always present; UPC/EAN scanning is a /search (SPA) scenario. Changing /import to a list would break the extension’s single-item contract for no benefit, so it is explicitly excluded.
  • The “input parsed to a valid ASIN but GetItems returned no product” branch — no barcode in the input to fall back to.
  • ISBN-13 / GTIN unification for books (ISBN matching stays exact); ISBN-10 precedence is unchanged.
  • Backend/operations changes — frontend-only.
  • UI changes beyond what the result path requires.
  1. Frontend-only; no backend API contract changes.
  2. Preserve existing error semantics — unresolved inputs return an empty result, not an error.
  3. Return all matching ASINs for an identifier (show-all), deduped by ASIN — mirror keyword-search behavior; do not collapse to a single hit.
  4. Observability follows the existing search.ts pattern (local addBreadcrumb on dispatch rungs, captureException on API errors) — no special always-on/lean carve-out for the new path.
  5. Follow the repo’s PR-body CHANGELOG convention (no direct CHANGELOG.md edit).
  • arda-frontend-app/src/server/routes/amazon/search.ts — search dispatch module (fallback insertion point).
  • arda-frontend-app/src/lib/shared/amazon/asin.ts — ASIN extraction helpers.
  • arda-frontend-app/src/server/lib/amazon/ — identifier classification and search-request builders.

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