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.
Linear Tickets
Section titled “Linear Tickets”- PDEV-1237 — Extend the Amazon product search to fallback to search by UPC if ASIN is not found: Add a UPC fallback to the Amazon search path when ASIN resolution yields no product. Child of PDEV-1212 (UX / Improvements).
Repositories
Section titled “Repositories”| Repository | Role | Planned Changes |
|---|---|---|
arda-frontend-app | Primary | Harden 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. |
documentation | Supporting | This project’s artifacts under reference-data/item/add-items-from-amazon/upc-fallback; advance status frontmatter through its lifecycle. |
Success Criteria
Section titled “Success Criteria”- 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. - 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.)
- 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).
- All matching ASINs for an identifier are returned (deduped by ASIN), matching keyword-search “show all” behavior — no collapsing to a single “best” hit.
- The SPA renders all matches for a UPC/EAN query via the existing
/searchresults list — no new import UI./importis unchanged. - Existing dispatch paths (single/multi-ASIN GetItems, keyword SearchItems, recognised rejections, ISBN-10-as-ASIN precedence) are unaffected.
- 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 lintandnpx tsc --noEmitpass.
Context
Section titled “Context”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:
- ASIN extract-and-dispatch — a strict
extractAsinon the query; on success it callsfetchCreatorsApiItems([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. - Multi-token ASIN — batch GetItems when every token is a valid ASIN.
- 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.
Search dispatch & fallback ladder
Section titled “Search dispatch & fallback ladder”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.
- ASIN / URL — input resolves to an ASIN (bare or Amazon product URL) →
GetItems([asin]). - 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.
- check digit) → identifier-mode
- 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.)
- 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.
In Scope
Section titled “In Scope”- 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.
filterByExternalIdsnormalization — 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.
Out of Scope
Section titled “Out of Scope”/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/importto 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
GetItemsreturned 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/
operationschanges — frontend-only. - UI changes beyond what the result path requires.
Constraints
Section titled “Constraints”- Frontend-only; no backend API contract changes.
- Preserve existing error semantics — unresolved inputs return an empty result, not an error.
- Return all matching ASINs for an identifier (show-all), deduped by ASIN — mirror keyword-search behavior; do not collapse to a single hit.
- Observability follows the existing
search.tspattern (localaddBreadcrumbon dispatch rungs,captureExceptionon API errors) — no special always-on/lean carve-out for the new path. - Follow the repo’s PR-body CHANGELOG convention (no direct
CHANGELOG.mdedit).
Reference Documents
Section titled “Reference Documents”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
Copyright: © Arda Systems 2025-2026, All rights reserved