# Amazon Unit Price & Pack Size Audit (`practical_ai_lab_jp/pal-amazon-unit-price-pack-size-audit`) Actor

Audit offer exports you already hold: normalize explicit pack counts and units, compute unit prices, and flag misleading comparisons and missing quantity data.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-amazon-unit-price-pack-size-audit.md
- **Developed by:** [Practical AI Lab](https://apify.com/practical_ai_lab_jp) (community)
- **Categories:** E-commerce, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 offer auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Amazon Unit Price & Pack Size Audit

Audit multi-seller offer exports that **you already hold and are authorized to analyze**. The Actor normalizes the pack counts and units you supply, computes comparable unit prices, and flags offers where the headline price is misleading or quantity data is missing.

It does **not** scrape Amazon or any other site, call any API, or fetch anything from the network. It only processes the JSON rows you give it.

### What it checks

- Normalizes explicit `packCount`, `unitSize` and `unit` (mg, g, kg, oz, lb, ml, cl, l, fl oz, count/each/piece) into a single base unit per measurement type.
- Computes unit price per 100 g, per 100 ml or per count.
- Compares offers only within the same product group, same currency and same measurement type.
- Flags:
  - `LOWEST_HEADLINE_PRICE_NOT_BEST_UNIT_PRICE` – the cheapest sticker price is not the cheapest per unit.
  - `WIDE_UNIT_PRICE_SPREAD` – best and worst unit price differ by at least your threshold.
  - `MISSING_PACK_COUNT`, `MISSING_UNIT`, `MISSING_UNIT_SIZE`, `UNKNOWN_UNIT`, `INVALID_*` – quantity data is missing or unusable, so no unit price is produced.
  - `MIXED_CURRENCY_GROUP`, `MIXED_UNIT_TYPES_IN_GROUP` – a group contains data that cannot be compared directly.
  - `SINGLE_COMPARABLE_OFFER`, `SMALL_SAMPLE`, `DUPLICATE_OFFER`.

### Input

Copy this into the **Offers** field:

```json
[
  {"asin": "B0SAMPLE01", "seller": "Seller A", "price": 12.99, "currency": "USD", "packCount": 1, "unitSize": 32, "unit": "oz"},
  {"asin": "B0SAMPLE01", "seller": "Seller B", "price": 9.99, "currency": "USD", "packCount": 1, "unitSize": 12, "unit": "oz"},
  {"asin": "B0SAMPLE01", "seller": "Seller C", "price": 24.5, "currency": "USD", "packCount": 2, "unitSize": 16, "unit": "oz"},
  {"asin": "B0SAMPLE01", "seller": "Seller D", "price": 11.5, "currency": "USD", "unitSize": 24, "unit": "oz"},
  {"asin": "B0SAMPLE02", "seller": "Seller A", "price": 8.0, "currency": "USD", "packCount": 4, "unit": "count"},
  {"asin": "B0SAMPLE02", "seller": "Seller B", "price": 14.0, "currency": "USD", "packCount": 12, "unit": "count"},
  {"asin": "B0SAMPLE02", "seller": "Seller E", "price": 12.0, "currency": "EUR", "packCount": 6, "unit": "count"}
]
```

| Field | Required | Notes |
| --- | --- | --- |
| `asin` or `productGroup` | one of them | Offers are compared within this key. Use `productGroup` to compare different ASINs of the same product. |
| `price` | yes | Positive number. |
| `currency` | yes | Three-letter code. Never converted. |
| `packCount` | yes | Whole number. Never assumed to be 1. |
| `unit` | yes | One of the supported units. |
| `unitSize` | for weight/volume | Size of one pack item. Optional for count units (defaults to 1). |
| `seller` | recommended | Used for labels and duplicate detection. |

Other options: `fieldMap` (map your column names to the standard ones), `priceSpreadThresholdPercent` (default 25), `maxOffers` (default 5000, max 20000).

### Output

One dataset row per input offer with normalized fields, `unitPrice`, `unitPriceBasis`, `unitPriceVsBestPercent`, `status` (`ok`, `needs_data`, `invalid`, `duplicate`) and `flags`. The key-value store record `OUTPUT` contains per-group summaries (best seller, spread, counts) and status totals.

### Pricing event

The code emits the event `offer-audited` once per offer with a computed unit price (`status: ok`). Invalid input, invalid rows, rows missing quantity data and duplicates are never charged. No payable plan is configured in this repository.

### Limitations

- Garbage in, garbage out: results are only as good as the supplied fields. Pack counts and sizes are **never inferred** from titles or descriptions, so rows without explicit structured quantities get no unit price.
- `oz` is treated as weight ounces and `fl oz` as US fluid ounces. Convert other conventions beforehand.
- All count-type units (each, piece, count) are treated as the same measure; sheets vs. capsules are not distinguished.
- No currency conversion, taxes, shipping, coupons, subscription discounts or availability are considered.
- Comparisons with only two offers are marked `SMALL_SAMPLE`; spread percentages from so few offers are indicative only and do not prove a product is overpriced.
- Offer data may be out of date by the time it is analyzed; the Actor cannot verify its accuracy or your right to use it.
- Runs are in memory: keep exports within `maxOffers`; extra rows are ignored and reported in `OUTPUT`.

### Local tests

Offline unit tests are in `tests/` (`python -m unittest discover -s tests`).

# Actor input Schema

## `offers` (type: `array`):

Offer rows you are authorized to analyze (for example from your own export). Each row is a JSON object with asin (or productGroup), seller, price, currency, packCount, unitSize and unit. Nothing is fetched from any website.

## `fieldMap` (type: `object`):

Map the standard field names (asin, productGroup, seller, price, currency, packCount, unitSize, unit) to the column names in your rows, for example {"price": "offer_price"}.

## `priceSpreadThresholdPercent` (type: `integer`):

Flag a comparison when the highest unit price exceeds the lowest by at least this percentage.

## `maxOffers` (type: `integer`):

Rows beyond this limit are ignored to keep memory low.

## Actor input object example

```json
{
  "offers": [
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller A",
      "price": 12.99,
      "currency": "USD",
      "packCount": 1,
      "unitSize": 32,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller B",
      "price": 9.99,
      "currency": "USD",
      "packCount": 1,
      "unitSize": 12,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller C",
      "price": 24.5,
      "currency": "USD",
      "packCount": 2,
      "unitSize": 16,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller D",
      "price": 11.5,
      "currency": "USD",
      "unitSize": 24,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE02",
      "seller": "Seller A",
      "price": 8,
      "currency": "USD",
      "packCount": 4,
      "unit": "count"
    },
    {
      "asin": "B0SAMPLE02",
      "seller": "Seller B",
      "price": 14,
      "currency": "USD",
      "packCount": 12,
      "unit": "count"
    },
    {
      "asin": "B0SAMPLE02",
      "seller": "Seller E",
      "price": 12,
      "currency": "EUR",
      "packCount": 6,
      "unit": "count"
    }
  ],
  "fieldMap": {},
  "priceSpreadThresholdPercent": 25,
  "maxOffers": 5000
}
```

# Actor output Schema

## `results` (type: `string`):

Download rows from the latest run in JSON/CSV.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "offers": [
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller A",
            "price": 12.99,
            "currency": "USD",
            "packCount": 1,
            "unitSize": 32,
            "unit": "oz"
        },
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller B",
            "price": 9.99,
            "currency": "USD",
            "packCount": 1,
            "unitSize": 12,
            "unit": "oz"
        },
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller C",
            "price": 24.5,
            "currency": "USD",
            "packCount": 2,
            "unitSize": 16,
            "unit": "oz"
        },
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller D",
            "price": 11.5,
            "currency": "USD",
            "unitSize": 24,
            "unit": "oz"
        },
        {
            "asin": "B0SAMPLE02",
            "seller": "Seller A",
            "price": 8,
            "currency": "USD",
            "packCount": 4,
            "unit": "count"
        },
        {
            "asin": "B0SAMPLE02",
            "seller": "Seller B",
            "price": 14,
            "currency": "USD",
            "packCount": 12,
            "unit": "count"
        },
        {
            "asin": "B0SAMPLE02",
            "seller": "Seller E",
            "price": 12,
            "currency": "EUR",
            "packCount": 6,
            "unit": "count"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-amazon-unit-price-pack-size-audit").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "offers": [
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller A",
            "price": 12.99,
            "currency": "USD",
            "packCount": 1,
            "unitSize": 32,
            "unit": "oz",
        },
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller B",
            "price": 9.99,
            "currency": "USD",
            "packCount": 1,
            "unitSize": 12,
            "unit": "oz",
        },
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller C",
            "price": 24.5,
            "currency": "USD",
            "packCount": 2,
            "unitSize": 16,
            "unit": "oz",
        },
        {
            "asin": "B0SAMPLE01",
            "seller": "Seller D",
            "price": 11.5,
            "currency": "USD",
            "unitSize": 24,
            "unit": "oz",
        },
        {
            "asin": "B0SAMPLE02",
            "seller": "Seller A",
            "price": 8,
            "currency": "USD",
            "packCount": 4,
            "unit": "count",
        },
        {
            "asin": "B0SAMPLE02",
            "seller": "Seller B",
            "price": 14,
            "currency": "USD",
            "packCount": 12,
            "unit": "count",
        },
        {
            "asin": "B0SAMPLE02",
            "seller": "Seller E",
            "price": 12,
            "currency": "EUR",
            "packCount": 6,
            "unit": "count",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-amazon-unit-price-pack-size-audit").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "offers": [
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller A",
      "price": 12.99,
      "currency": "USD",
      "packCount": 1,
      "unitSize": 32,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller B",
      "price": 9.99,
      "currency": "USD",
      "packCount": 1,
      "unitSize": 12,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller C",
      "price": 24.5,
      "currency": "USD",
      "packCount": 2,
      "unitSize": 16,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE01",
      "seller": "Seller D",
      "price": 11.5,
      "currency": "USD",
      "unitSize": 24,
      "unit": "oz"
    },
    {
      "asin": "B0SAMPLE02",
      "seller": "Seller A",
      "price": 8,
      "currency": "USD",
      "packCount": 4,
      "unit": "count"
    },
    {
      "asin": "B0SAMPLE02",
      "seller": "Seller B",
      "price": 14,
      "currency": "USD",
      "packCount": 12,
      "unit": "count"
    },
    {
      "asin": "B0SAMPLE02",
      "seller": "Seller E",
      "price": 12,
      "currency": "EUR",
      "packCount": 6,
      "unit": "count"
    }
  ]
}' |
apify call practical_ai_lab_jp/pal-amazon-unit-price-pack-size-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,practical_ai_lab_jp/pal-amazon-unit-price-pack-size-audit"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/6Xh4jSGrAMcONMKuY/builds/iDgqcFvxMCC5XCuph/openapi.json
