Home / Blog / Amazon API Price Format: Cents vs Decimal Across 3 Endpoints

Amazon API Price Format: Cents vs Decimal Across 3 Endpoints

/amazon-product-statistics returns buyBoxPrice as an integer in cents, while /amazon-product-lookup and /amazon-product-offers return decimal currency units, and offers.buyBox.totalPrice is an unrounded float on top of that. A tested Python script normalises all 3 into one comparable price and flags a mismatch when the conversion is missed, the class of bug that makes a mixed-endpoint total 100 times too high.

October 28, 2026
5 min read
SellerMagnet Team
Share & Bookmark
Terminal window with Python functions that convert 3 Amazon API price formats to one decimal value

/amazon-product-statistics returns its buyBoxPrice as an integer in minor units: 41800 means 418.00. /amazon-product-lookup and /amazon-product-offers return the same kind of price already in decimal currency units, such as 418.00, and /amazon-product-offers additionally returns an unrounded float sum for buyBox.totalPrice. Mixing these without converting first makes a total come out 100 times too high or carries a float tail into a stored price.

Key Takeaways

  • /amazon-product-statistics buyBoxPrice is an integer in cents (41800 = 418.00), or the string "N/A" when nobody holds the Buy Box.
  • /amazon-product-lookup productInfo.buyBoxInfo.price is already decimal currency units (418.00), or "N/A" with no featured offer.
  • /amazon-product-offers buyBox.totalPrice is decimal units but an unrounded float sum of price and shipping (444.97999999999996); offers[].totalPrice is rounded, buyBox.totalPrice is not.
  • Skipping the statistics /100 conversion is the specific bug class that makes a mixed-endpoint price total 100 times too high; it never raises an error.
  • Each endpoint shapes "nobody holds the Buy Box" differently: a string "N/A", another string "N/A", or an empty buyBox object with buyBoxSuppressed true.

Which Amazon API endpoint returns price in cents, and which in decimal?

Only one of the 3 endpoints that carry a Buy Box price uses minor units. /amazon-product-statistics buyBoxPrice is an integer: 41800 means 418.00, divide by 100 to get the decimal amount. /amazon-product-lookup productInfo.buyBoxInfo.price and /amazon-product-offers buyBox.totalPrice are already decimal currency units, so dividing those by 100 would be the opposite mistake. The table below is the full reference for all 3 fields.

Amazon API price field formats for the Buy Box price (October 2026).
EndpointFieldRaw exampleFormatNormalised
/amazon-product-statisticsbuyBoxPrice41800integer, minor units (cents)418.00
/amazon-product-lookupproductInfo.buyBoxInfo.price418.0decimal currency units418.00
/amazon-product-offersbuyBox.totalPrice444.97999999999996decimal, unrounded float sum444.98
Blueprint of an amazon-product-statistics request highlighting buyBoxPrice as an integer in cents
buyBoxPrice is the only one of the 3 fields that is not already a decimal amount.

offers.buyBox.totalPrice needs rounding, not a unit conversion

buyBox.totalPrice from /amazon-product-offers is already in decimal currency units, so it needs no /100 conversion, but it is a raw float sum of the item price plus shipping and can read as 444.97999999999996 instead of 444.98. offers[].totalPrice, the field on each individual offer in the same response, is rounded to 2 decimals by the backend; buyBox.totalPrice is not, so round it on the client before storing or displaying it, the same round(x, 2) used for the other 2 fields in the table above.

The same ASIN's Buy Box price read from all 3 endpoints side by side (illustrative values)

{
  "amazon-product-statistics": {"buyBoxPrice": 41800},
  "amazon-product-lookup": {"productInfo": {"buyBoxInfo": {"price": 418.0}}},
  "amazon-product-offers": {"buyBox": {"totalPrice": 418.0}}
}

No Buy Box holder: three different null shapes

When nobody currently holds the Buy Box, each endpoint says so in its own way: /amazon-product-statistics returns the string "N/A" for buyBoxPrice, /amazon-product-lookup returns the string "N/A" for productInfo.buyBoxInfo.price, and /amazon-product-offers returns buyBoxSuppressed: true with buyBox as an empty object, no totalPrice key at all. A price reader that only checks if price: can still crash or silently return 0 on 3 different shapes of the same real-world case; check the type (statistics, lookup) or the flag and key presence (offers) instead.

How do I reconcile all 3 formats in one script?

Normalise each endpoint's price with its own rule, then compare: /100 for statistics, pass-through for lookup, round-to-2dp for offers. The script below calls all 3 endpoints for a list of ASINs, normalises each price, and flags a result where the 3 normalised prices disagree by more than a 5x factor, the signature of a unit conversion that was missed somewhere upstream. get_with_retry and new_session come from retry.py and sellermagnet.py, both published in the Python quickstart.

Install requests, set the key, run the script

pip install requests
export SELLERMAGNET_API_KEY="your-key-here"
python3 reconcile_prices.py

reconcile_prices.py: normalise the Buy Box price from 3 endpoints and flag a unit mismatch

"""Reconcile Amazon Buy Box prices across 3 SellerMagnet endpoints that use 3 different price formats.

/amazon-product-statistics buyBoxPrice is an INTEGER in minor units (cents): 41800 means 418.00. A client that
treats that integer as if it were already a decimal amount reports a price 100x too high. /amazon-product-lookup
productInfo.buyBoxInfo.price and /amazon-product-offers buyBox.totalPrice are already decimal currency units
(444.99), but offers.totalPrice is a raw float sum of item price + shipping and is not pre-rounded (it can read
444.97999999999996), so it needs client-side rounding, not a unit conversion.
Helpers: sellermagnet.py and retry.py from https://sellermagnet-api.com/blog/amazon-product-api-python
"""
import re
from collections import Counter

import requests

from retry import get_with_retry
from sellermagnet import ApiError, new_session

MARKETPLACE = "ATVPDKIKX0DER"   # amazon.com
ASIN_FILE = "asins.txt"         # one ASIN per line
MAX_FAILED = 3                  # 404/502/503 are billed on every attempt: stop after 3 ASINs that keep failing


def read_asins(path):
    asins, rejected = [], []
    with open(path, encoding="utf-8-sig") as fh:
        for line in fh:
            value = line.strip().upper()
            if value and not re.fullmatch(r"[A-Z0-9]{10}", value):
                rejected.append(value)    # billed anyway: statistics 400s it, lookup/offers send it upstream
            elif value and value not in asins:
                asins.append(value)       # each ASIN once: a repeat would cost 3 more credits
    return asins, rejected


def stats_price(data):
    """/amazon-product-statistics buyBoxPrice: integer minor units, or the string 'N/A' with no Buy Box holder."""
    raw = data.get("buyBoxPrice")
    if not isinstance(raw, int):          # "N/A" (string) when nobody holds the Buy Box
        return None, raw
    return round(raw / 100, 2), raw


def lookup_price(data):
    """/amazon-product-lookup productInfo.buyBoxInfo.price: decimal units already, or 'N/A' with no featured offer."""
    raw = (data.get("productInfo") or {}).get("buyBoxInfo", {}).get("price")
    if not isinstance(raw, (int, float)):  # "N/A" (string)
        return None, raw
    return round(float(raw), 2), raw


def offers_price(data):
    """/amazon-product-offers buyBox.totalPrice: decimal units, unrounded float sum; {} when buyBoxSuppressed."""
    box = data.get("buyBox") or {}
    if data.get("buyBoxSuppressed") or "totalPrice" not in box:
        return None, box
    return round(box["totalPrice"], 2), box["totalPrice"]


# A mismatch beyond this factor between two endpoints that both claim a current Buy Box price almost always means
# one of them was read as the wrong format (most commonly: statistics cents used without the /100 conversion).
MISMATCH_FACTOR = 5.0


def reconcile(asin, stats, lookup, offers):
    s_price, s_raw = stats_price(stats)
    l_price, l_raw = lookup_price(lookup)
    o_price, o_raw = offers_price(offers)
    prices = [p for p in (s_price, l_price, o_price) if p is not None]
    suspect = False
    if len(prices) >= 2:
        suspect = max(prices) / min(prices) >= MISMATCH_FACTOR if min(prices) > 0 else False
    return {
        "asin": asin,
        "statistics": s_price, "statistics_raw": s_raw,
        "lookup": l_price, "lookup_raw": l_raw,
        "offers": o_price, "offers_raw": o_raw,
        "suspect": suspect,
    }


def row(r):
    def cell(price, raw):
        if price is None:
            return f"-  (raw {raw!r})"
        return f"{price:.2f}  (raw {raw!r})"
    flag = "  <-- CHECK UNITS" if r.get("suspect") else ""
    print(f"{r['asin']:<10}  stats={cell(r['statistics'], r['statistics_raw']):<28}  "
          f"lookup={cell(r['lookup'], r['lookup_raw']):<22}  offers={cell(r['offers'], r['offers_raw'])}{flag}")


def summary(rows):
    print()
    held = [r for r in rows if r["statistics"] is not None]
    print(f"ASINs checked: {len(rows)}, Buy Box held per statistics: {len(held)}, "
          f"flagged for a unit mismatch: {sum(r['suspect'] for r in rows)}")


def main():
    asins, rejected = read_asins(ASIN_FILE)
    if rejected:
        print("skipped, not an ASIN: " + ", ".join(rejected))
    session = new_session()
    rows, failed, gates = [], 0, Counter()
    for asin in asins:
        try:
            stats = get_with_retry(session, "amazon-product-statistics", asin=asin, marketplaceId=MARKETPLACE)
            lookup = get_with_retry(session, "amazon-product-lookup", asin=asin, marketplaceId=MARKETPLACE)
            offers = get_with_retry(session, "amazon-product-offers", asin=asin, marketplaceId=MARKETPLACE)
        except ApiError as err:
            if err.status in (400, 401, 403):     # parameter, key or credits problem: it repeats on every ASIN
                print(f"stopped: {err}")
                break
            failed += 1
            gates[err.status] += 1
            print(f"{asin:<10}  error {err.status}: {err}")
            if failed >= MAX_FAILED:
                print(f"stopped after {failed} failed ASINs")
                break
            continue
        except requests.RequestException as err:
            failed += 1
            print(f"{asin:<10}  {type(err).__name__}")
            if failed >= MAX_FAILED:
                print(f"stopped after {failed} failed ASINs")
                break
            continue
        r = reconcile(asin, stats, lookup, offers)
        rows.append(r)
        row(r)
    summary(rows)


if __name__ == "__main__":
    main()

Output of reconcile_prices.py (illustrative ASINs: a Buy Box held, no holder, and an unrounded float)

B0CL61F39H  stats=418.00  (raw 41800)           lookup=418.00  (raw 418.0)     offers=418.00  (raw 418.0)
B0D1XYZ002  stats=-  (raw 'N/A')                lookup=-  (raw 'N/A')          offers=-  (raw {})
B0D1XYZ003  stats=444.98  (raw 44498)           lookup=444.98  (raw 444.98)    offers=444.98  (raw 444.97999999999996)

ASINs checked: 3, Buy Box held per statistics: 2, flagged for a unit mismatch: 0
Table of 3 illustrative ASINs with their statistics, lookup and offers price normalised to the same decimal value
All 3 endpoints agree once each price is normalised to its own rule.

A missed /100 never raises an error

Forgetting to divide statistics buyBoxPrice by 100 produces a valid-looking number, not an exception, so it reaches a report or a repricer unnoticed until someone spots a price 100 times too high. The reconcile() sanity check above compares the 3 normalised prices and flags any pair more than 5x apart, which catches this specific bug class before it ships.

What does the API charge for checking all 3 endpoints?

Each endpoint is billed on its own at 1 credit per request, so reconciling one ASIN across statistics, lookup and offers costs 3 credits. /amazon-product-statistics is the one endpoint of the 3 that validates ASIN format and bills a 400 for a malformed one; /amazon-product-lookup and /amazon-product-offers send a malformed ASIN upstream and bill it like any other request, so filtering obviously bad ASINs client-side, as read_asins() does above, saves credits on all 3 calls, not just one.

Status codes across the 3 endpoints and whether they are billed.
StatusMeaningBilled
200all 3 calls answeredyes, 3 credits
400 gatemissing or invalid marketplaceIdno
400 endpoint (statistics only)malformed ASINyes
401 / 403key or creditsno
404ASIN not on this marketplaceyes, not retried
429per-key concurrency capno
500internal errorno, refunded, retried once
502 / 503upstream erroryes, every attempt

Frequently Asked Questions

Why is the Buy Box price from /amazon-product-statistics 100 times too high?

buyBoxPrice from /amazon-product-statistics is an integer in minor units (cents): 41800 means 418.00. Divide by 100 before using it as a decimal amount, or treat it as if it were already dollars and cents and every total comes out 100 times too high.

Do /amazon-product-lookup and /amazon-product-offers also return cents?

No. productInfo.buyBoxInfo.price from /amazon-product-lookup and buyBox.totalPrice from /amazon-product-offers are already decimal currency units, such as 418.00. Only /amazon-product-statistics uses minor units for buyBoxPrice.

Why does offers.buyBox.totalPrice need rounding if it is already decimal?

buyBox.totalPrice is an unrounded float sum of the item price plus shipping, so it can read as 444.97999999999996. offers[].totalPrice (the full offer list) is rounded to 2 decimals by the backend; buyBox.totalPrice is not, so round it client-side.

What does a price field look like when nobody holds the Buy Box?

Each endpoint shapes it differently: /amazon-product-statistics returns the string "N/A" for buyBoxPrice, /amazon-product-lookup returns the string "N/A" for productInfo.buyBoxInfo.price, and /amazon-product-offers returns an empty object for buyBox with buyBoxSuppressed set to true. Check the type or the flag, not just truthiness.

Does checking all 3 endpoints for one ASIN cost more credits?

Yes. Each endpoint is billed separately at 1 credit per request, so reconciling the same ASIN across statistics, lookup and offers costs 3 credits total, the same as calling each one once on its own.

Bottom line: /amazon-product-statistics buyBoxPrice is cents, divide by 100; /amazon-product-lookup and /amazon-product-offers are already decimal, but offers needs rounding; and all 3 shape a missing Buy Box differently. Normalise each one with its own rule before comparing or summing them. A free account comes with 150 credits after e-mail verification, and every field above is in the API documentation.

Ready to Extract Amazon Data at Scale?

Start building with SellerMagnet API today. Real-time product data, competitive pricing, and review analytics at your fingertips.

150 free API credits • No credit card required • Cancel anytime