# UPC and barcode lookup APIs: how lookups work

> How a UPC and barcode lookup API works: barcode formats and check digits, how a code is matched to a product, shared barcodes, batch lookups and prices.

*Source: https://shopsavvy.com/learn/barcode-lookup-api · Published 2026-10-12*

---

## What a barcode lookup returns

A barcode lookup API takes the number printed under a product’s barcode and returns the product it belongs to: name, brand, category, images and the other identifiers it is known by. Some APIs stop there; others also return current prices from retailers and price history for the same product.

Two products by UPC in one request (ShopSavvy Data API):

```bash
curl "https://api.shopsavvy.com/v1/products?ids=611247373064,611247369449" \
  -H "Authorization: Bearer $SHOPSAVVY_API_KEY"
```

A lookup costs 1 credit per product found. Codes that match nothing are left out of the response and cost nothing, and if nothing in the request matches at all, the API answers ERR_PRODUCT_NOT_FOUND so your app can fall back to manual entry. The catalog behind it holds 30M+ products.

## Barcode formats and normalizing them

| Format | Digits | Where you see it |
| --- | --- | --- |
| UPC-A | 12 | Most products sold in the US and Canada |
| EAN-13 | 13 | Most of the rest of the world; a UPC-A with a 0 in front is the same code |
| ISBN-13 | 13 (starts 978 or 979) | Books |
| UPC-E / EAN-8 | 8 | Small packages; a UPC-E is a compressed UPC-A |
| GTIN-14 | 14 | Shipping cases; a leading 0 means the same item as the 13 digits after it |

Normalize before you send: strip spaces and dashes, keep codes as strings (a numeric type drops the leading zeros a UPC often starts with), and expand UPC-E to its UPC-A form if your catalog stores the long form. In ShopSavvy responses the barcode field is a number, so pad it back to 12 or 13 digits before you compare or display it.

## Validating the check digit first

The last digit of every UPC and EAN is computed from the others, so most typos and misreads can be caught before you spend a request. Any single wrong digit breaks the check, and so do most pairs of neighboring digits swapped.

```typescript
// True when the last digit of a UPC-A, EAN-13, EAN-8 or GTIN-14 matches the others.
// Starting next to the check digit, every other digit counts 3 times.
function checkDigitValid(code: string): boolean {
  const digits = code.replace(/[\s-]/g, '')
  if (!/^(\d{8}|\d{12}|\d{13}|\d{14})$/.test(digits)) return false
  const body = digits.slice(0, -1).split('').reverse().map(Number)
  const sum = body.reduce((total, digit, index) => total + digit * (index % 2 === 0 ? 3 : 1), 0)
  return (10 - (sum % 10)) % 10 === Number(digits[digits.length - 1])
}
```

## How a code is matched to a product

In the ShopSavvy Data API, every identifier in ids is tried against these fields in order, and the first field that matches wins:

1. Barcode. A full-length UPC-A or EAN-13 with a valid check digit is looked up only as a barcode, never as a model number that happens to have the same digits.
2. ShopSavvy product ID.
3. Model number, then MPN.
4. Amazon ASIN.
5. A retailer product URL, with tracking parameters stripped.
6. Product name, as a full-text best guess.

Every product in the response carries a match object that says how it was found:

```json
"match": { "identifier": "611247373064", "by": "barcode", "candidates": 1 }
```

candidates above 1 means the code is shared by several catalog records, usually because a manufacturer reused it or a retailer listed the item twice. The API then returns the record with the most evidence behind it (the most verified offers on file), and the same one every time. Flag those results for a person to confirm if a wrong match would be costly.

## Looking up many barcodes

- Put up to 100 comma-separated codes in one ids parameter; line results up with your list by match.identifier, since misses are left out.
- POST /v1/products/batch also takes up to 100 identifiers: up to 20 return immediately, and a larger batch returns a batch ID you poll at no charge.
- Add format=csv to get a flat file you can open in a spreadsheet or load into a catalog.
- Plan around the rate limit: 1,000 requests per rolling hour per account.

## From product to prices

A lookup tells you what a product is. To see what it costs, call the offers endpoint with the same code: offers from tens of thousands of retailers, each with its price, condition, availability, link and the time it was seen (3 credits per product that has offers). Add a date window for price history.

[Free UPC lookup](https://shopsavvy.com/tools/upc-lookup): Checking one code by hand? The free lookup shows the product, brand, category and photo with no account.

## Frequently asked questions

**Is there a free UPC lookup?** Yes. shopsavvy.com/tools/upc-lookup looks up one barcode at a time with no account, within per-visitor limits. For lookups from your own code or in bulk, use the Data API.

**Does a barcode lookup API return prices?** Some do. With ShopSavvy, the product endpoint returns the product record and the offers endpoint returns current prices from retailers for the same barcode, with price history available by date window.

**What happens when a barcode is not found?** Codes that match nothing are left out of the response and cost nothing. If no code in the request matches, the API returns ERR_PRODUCT_NOT_FOUND, so your app can ask for the product another way.

**Can I look up books by ISBN?** Yes, by ISBN-13, the 13-digit number starting with 978 or 979 that is printed under a book’s barcode.

## Related

- [Product data API guide](https://shopsavvy.com/learn/product-data-api-guide)
- [Free UPC lookup](https://shopsavvy.com/tools/upc-lookup)
- [Barcode lookup for POS and catalogs](https://shopsavvy.com/data/for-pos)
- [ShopSavvy vs Barcode Lookup](https://shopsavvy.com/compare/shopsavvy-data-api-vs-barcode-lookup)
- [Data API documentation](https://shopsavvy.com/data/documentation)
- [Data API playground](https://shopsavvy.com/data/playground)
