ProductMapper Python SDK: UPC to ASIN Lookup API Client
Official Python client for the ProductMapper API. Convert a UPC, EAN, GTIN, ASIN or product title into live Amazon listing data: price, sales rank (BSR), offer counts, Buy Box status, brand, category and images, across 16 Amazon marketplaces.
Use it to build barcode to ASIN lookup, retail arbitrage tooling, competitor price monitoring, catalog enrichment, and product data pipelines, with sync and async clients and full type hints.
Website - API Documentation - Get a Free API Key - Node.js SDK
Contents
- Why ProductMapper
- Install
- Quick start
- Convert UPC to ASIN
- Bulk UPC to ASIN conversion
- Lookup history
- Async client
- Error handling
- Configuration
- API reference
- FAQ
Why ProductMapper
| Feature | Detail |
|---|---|
| Identifier types | UPC, EAN, GTIN, ASIN, free-text title, or auto detection |
| Amazon marketplaces | 16 regions including US, CA, UK, DE, FR, IT, ES, JP, AU, IN |
| Batch size | Up to 500 identifiers per background job, with CSV export |
| Data returned | Price, list price, sales rank, offer counts, FBA/merchant split, Buy Box, brand, category, images |
| Clients | Synchronous and asyncio, both fully type hinted |
| Python | 3.9 through 3.13 |
Install
pip install productmapper
uv add productmapper
# or
poetry add productmapper
Requires Python 3.9 or newer. Ships py.typed for full editor and mypy support.
Quick start
import os
from productmapper import ProductMapper
client = ProductMapper(api_key=os.environ["PRODUCTMAPPER_API_KEY"])
result = client.lookup(value="753933140816", type="UPC")
print(result.marketplace_id) # "B09Z2J1MP2" (the matched ASIN)
print(result.title) # "Husky Liners Weatherbeater Floor Mats"
print(result.price) # 80.99
print(result.listing_details.sales_rank) # 67364
Get a free API key at
product-mapper.com/dashboard/api-keys. Keys look like
pm_live_... and belong in an environment variable, never in source control.
The client is also a context manager, which closes the connection pool on exit:
with ProductMapper(api_key=...) as client:
result = client.lookup(value="753933140816")
Convert UPC to ASIN
type defaults to auto, so the server detects whether you passed a UPC, EAN, GTIN or ASIN.
client.lookup(value="753933140816") # auto-detected
client.lookup(value="753933140816", type="UPC") # UPC to ASIN
client.lookup(value="0885909950805", type="EAN") # EAN to ASIN
client.lookup(value="B09Z2J1MP2", type="ASIN") # ASIN lookup
client.lookup(value="Logitech MX Master 3S", type="Title") # title search
client.lookup(value="753933140816", region="DE") # scope to one marketplace
Each successful mapping costs one credit. Without a region, an identifier that matches in several
Amazon marketplaces returns them all, and still costs a single credit. Use .matches to handle the
one-match and many-match cases the same way:
result = client.lookup(value="753933140816")
for match in result.matches:
print(match.amazon_marketplace_label, match.listing_details.formatted_price)
Full listing fields
listing = result.listing_details
listing.asin # "B09Z2J1MP2"
listing.title # product title
listing.brand # "Husky Liners"
listing.price # 80.99
listing.list_price # 89.99
listing.formatted_price # "$80.99"
listing.sales_rank # 67364 (Best Sellers Rank)
listing.offer_count # 5
listing.offer_count_fba # 1
listing.offer_count_merchant # 4
listing.is_buy_box_winner # True
listing.category # "Floor Mats"
listing.category_group # "Automotive Parts and Accessories"
listing.image_url # product image
listing.link # Amazon product URL
Any field the API adds later is still reachable through listing.raw["newField"].
Slow lookups
If a lookup takes more than 8 seconds the API returns a job instead of a result. The client polls that
job automatically, so lookup() always returns a result. To manage polling yourself:
queued = client.lookup(value="753933140816", poll=False)
if queued.status == "processing":
result = client.wait_for_job(queued.job_id)
Bulk UPC to ASIN conversion
Submit up to 500 identifiers as one background job:
job = client.lookup_many(["753933140816", "B09Z2J1MP2", "Logitech MX Master 3S"])
finished = client.wait_for_batch(
job.id,
on_progress=lambda j: print(f"{j.processed_items}/{j.total_items}"),
)
for item in finished.items:
print(item.identifier_value, item.title, item.price, item.status)
Export results as CSV, ready for Excel or Google Sheets:
from pathlib import Path
csv_text = client.get_batch_csv(job.id)
Path("asin-results.csv").write_text(csv_text, encoding="utf-8")
Batch rows carry fewer fields than a single lookup. Look an identifier up individually when you need
link, category, identifiers or the full offer breakdown.
Lookup history
Every lookup is recorded, 25 rows per page.
page = client.history(page=1, search="husky")
print(page.total, page.total_pages)
for row in page:
print(row.identifier_value, row.title, row.status)
# Or walk every page, one row at a time.
for row in client.history_all():
print(row.identifier_value)
client.delete_history_row(row_id)
client.clear_history()
History rows report status as success or not_found, while batch items report completed.
Async client
AsyncProductMapper mirrors the sync client method for method.
import asyncio
from productmapper import AsyncProductMapper
async def main():
async with AsyncProductMapper(api_key=...) as client:
result = await client.lookup(value="753933140816", type="UPC")
print(result.title)
# Resolve many identifiers concurrently.
results = await asyncio.gather(
*(client.lookup(value=v) for v in ["753933140816", "B09Z2J1MP2"]),
return_exceptions=True,
)
async for row in client.history_all():
print(row.identifier_value)
asyncio.run(main())
Error handling
Every failure is a ProductMapperError, so one except can cover them all, with subclasses for the
cases worth handling individually.
from productmapper import (
NotFoundError,
RateLimitError,
CreditsExhaustedError,
)
try:
result = client.lookup(value="753933140816")
except NotFoundError:
print("No match in the Amazon catalog.")
except CreditsExhaustedError:
print("Out of credits: upgrade the plan or buy a credit pack.")
except RateLimitError as exc:
print(f"Retry in {exc.retry_after}s, limit is {exc.limit}/min")
| Exception | HTTP | Raised when |
|---|---|---|
ValidationError |
400 | Bad arguments, rejected before or by the API |
AuthenticationError |
401 | API key missing, malformed or revoked |
PermissionError |
403 | No active organization selected |
CreditsExhaustedError |
403 | Credit balance is empty |
NotFoundError |
404 | No catalog match, or the resource is not yours |
RateLimitError |
429 | Plan requests-per-minute exceeded |
ServerError |
5xx | The API failed to handle the request |
TimeoutError |
- | A request or polling loop ran out of time |
ConnectionError |
- | The request never reached the API |
JobFailedError |
- | A queued lookup or batch ended in a failed state |
Rate limits, server errors and network failures are retried automatically with exponential backoff,
honoring Retry-After. Validation and auth failures are never retried.
PermissionError, TimeoutError and ConnectionError deliberately shadow the builtins of the same
name. Import them from productmapper to catch the API versions.
Configuration
client = ProductMapper(
api_key=os.environ["PRODUCTMAPPER_API_KEY"],
timeout=30.0, # per request, in seconds
max_retries=2, # for 429, 5xx and network errors
headers={"X-Team": "pricing"}, # sent with every request
)
API reference
| Method | Description |
|---|---|
lookup(value, ...) |
Resolve one identifier. Polls a queued lookup unless poll=False |
lookup_many(items, ...) |
Submit up to 500 identifiers as a batch job |
get_job(job_id) |
Poll one queued single lookup |
get_jobs(job_ids) |
Poll up to 100 queued lookups in one round trip |
get_batch(batch_id) |
Fetch a batch job and its items |
get_batch_csv(batch_id) |
Export a batch job as CSV |
wait_for_job(job_id, ...) |
Poll a queued lookup until it resolves |
wait_for_batch(batch_id, ...) |
Poll a batch until every item is processed |
history(page=1, search=None) |
List lookup history, 25 per page |
history_all(search=None) |
Iterator over every history row |
delete_history_row(row_id) |
Delete one history row |
clear_history() |
Clear the entire history |
Identifier types: auto, UPC, EAN, GTIN, ASIN, Title
Amazon marketplaces: US, CA, MX, BR, UK, DE, FR, IT, ES, NL, PL, SE, IN,
JP, AU, SG
Both are exported as IDENTIFIER_TYPES and REGIONS.
Examples
Runnable scripts live in examples/: single lookup, batch with progress and CSV export, history paging, async usage, and full error handling.
FAQ
How do I convert a UPC to an ASIN in Python?
Install the package, create a client with your API key, and call
client.lookup(value="<upc>", type="UPC"). The matched ASIN is result.marketplace_id.
Can I look up many barcodes at once?
Yes. lookup_many() accepts up to 500 identifiers per batch job, and get_batch_csv() exports
results as CSV.
Does it support asyncio?
Yes. AsyncProductMapper mirrors the sync client method for method.
Which Amazon marketplaces are supported?
16 regions, listed above. Pass region to scope a lookup, or omit it to search across regions.
Does it work with pandas?
Yes. Batch items and history rows expose plain attributes and a raw dict, so
pd.DataFrame([item.raw for item in job.items]) works directly.
Is there a free plan? Yes, see pricing.
Is there a Node.js version? Yes, @siktec-lab/productmapper on npm (source).
Related
- ProductMapper REST API documentation
- MCP server for AI agents
- Node.js and TypeScript SDK
- Report an issue
License
MIT, see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file productmapper-1.2.1.tar.gz.
File metadata
- Download URL: productmapper-1.2.1.tar.gz
- Upload date:
- Size: 17.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3076ece81adc8d90f2c71be9a11a902ef92ea75f0e3218e8dc5ed5664880568f
|
|
| MD5 |
78dc9a1c1d5512aaa2369444b63c9dd4
|
|
| BLAKE2b-256 |
3174404070de0d0cb5997a0dae5a4d21372252110d00a393a74e9a3b934fe2d0
|
Provenance
The following attestation bundles were made for productmapper-1.2.1.tar.gz:
Publisher:
release.yml on siktec-lab/product-mapper-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
productmapper-1.2.1.tar.gz -
Subject digest:
3076ece81adc8d90f2c71be9a11a902ef92ea75f0e3218e8dc5ed5664880568f - Sigstore transparency entry: 2880077720
- Sigstore integration time:
-
Permalink:
siktec-lab/product-mapper-py@9e899575bf70ff62320ecac19ba9303cee5c561c -
Branch / Tag:
refs/tags/v1.2.1 - Owner: https://github.com/siktec-lab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9e899575bf70ff62320ecac19ba9303cee5c561c -
Trigger Event:
push
-
Statement type:
File details
Details for the file productmapper-1.2.1-py3-none-any.whl.
File metadata
- Download URL: productmapper-1.2.1-py3-none-any.whl
- Upload date:
- Size: 19.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de256180dffda93841bedca651bc00563effc04a95930002da0fa2871564b29e
|
|
| MD5 |
639a7f66370bf0e5a3be5f36595a25b0
|
|
| BLAKE2b-256 |
e2f7fd82eb83f45be40ec8c873db1e6306e0d9ace2e59438057e70a7c236ae52
|
Provenance
The following attestation bundles were made for productmapper-1.2.1-py3-none-any.whl:
Publisher:
release.yml on siktec-lab/product-mapper-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
productmapper-1.2.1-py3-none-any.whl -
Subject digest:
de256180dffda93841bedca651bc00563effc04a95930002da0fa2871564b29e - Sigstore transparency entry: 2880077736
- Sigstore integration time:
-
Permalink:
siktec-lab/product-mapper-py@9e899575bf70ff62320ecac19ba9303cee5c561c -
Branch / Tag:
refs/tags/v1.2.1 - Owner: https://github.com/siktec-lab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9e899575bf70ff62320ecac19ba9303cee5c561c -
Trigger Event:
push
-
Statement type: