# Shopify Category and Price Band Summary (`practical_ai_lab_jp/pal-shopify-category-price-bands`) Actor

Summarize price distributions, price bands, product types and vendors from a limited public Shopify feed sample, without guessing currency.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-shopify-category-price-bands.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 $2.00 / 1,000 shop summaries

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

## Shopify Category and Price Band Summary

Turns a limited sample of a Shopify store's public product feed (`/products.json`) into one compact summary row per shop: price statistics, price bands, and counts by product type and vendor.

It is built for quick catalog positioning checks: "Where does this shop sit on price, and what does it mostly sell?" It deliberately returns **summaries, not product dumps**, and it **never guesses a currency**.

### What you get per shop

- Sample size, priced-product count, and whether the sample cap was reached.
- Price statistics: min, 25th percentile, median, 75th percentile, max, mean (lowest positive variant price per product).
- Price bands: your own edges, or automatic quartile bands when at least 8 priced products exist.
- Top product types and top vendors with counts, plus `otherCount` and `unlabeledCount`.
- Plain-language `caveats` generated from the actual sample.

### Input example

```json
{
    "shopDomains": ["allbirds.com"],
    "maxProductsPerShop": 250,
    "priceBandEdges": ["50", "100", "150"],
    "topN": 10,
    "requestDelaySeconds": 1
}
```

Replace the example domain with shops you are permitted to analyze. Only the domain is used.

### Output shape (illustrative, not real data)

```json
{
    "shop": "shop.example.com",
    "status": "ok",
    "currency": null,
    "sampledProducts": 250,
    "sampleCapReached": true,
    "priceStats": {"min": "...", "median": "...", "max": "..."},
    "priceBands": [{"label": "below 50.00", "productCount": 0, "share": 0.0}],
    "productTypes": {"distinct": 0, "top": [], "otherCount": 0, "unlabeledCount": 0},
    "caveats": ["..."]
}
```

### Currency handling

The public feed contains numbers but no currency code. This Actor reports `currency: null`, shows prices exactly as the feed gives them, and never converts or merges them across shops. Compare bands between shops only if you know they share a currency.

### Safety and politeness

- HTTPS only, public domain names only. IP addresses, ports, credentials and local/internal names are rejected.
- DNS must resolve to public addresses; redirects are followed only within the same host.
- `robots.txt` is checked for the feed path (wildcards and longest-match rules supported). If it cannot be read, the shop is skipped.
- Requests are sequential with a delay of at least 0.5 s, response size is capped at 5 MB, and no proxies are used.
- Low memory: products are streamed into counters; only one number per priced product is kept.

### Pricing event

The code emits one `shop-summary` event per successfully summarized shop. Shops that fail (robots disallow, missing feed, rate limit, invalid data, empty feed) produce an error row and are **not** charged. The event price is not configured in this repository.

### Limitations and sample-size caveats

- Maximum 1,000 products per shop and 20 shops per run. Large catalogs are sampled in the store's own feed order, which is **not random** and may favor new, best-selling or alphabetical items.
- Percentiles and band shares from fewer than about 30 priced products are rough; the output says so.
- The summary uses the lowest positive variant price per product; compare-at prices, discounts, taxes, shipping and market-specific pricing are not captured.
- Product type and vendor are merchant-entered free text; spelling variants beyond case and spacing are not merged.
- Some stores disable or restrict `/products.json`, use a different public domain than their `myshopify.com` address, or block automated access. Those shops return an error row.
- Hidden, unpublished or password-protected products are not visible.
- DNS is validated before each request, but a hostile DNS server could in theory change answers between lookup and connection.
- The results describe a snapshot of public listings. They are not sales data, revenue, or proof of demand.

### Local tests

Tests are offline and use only the Python standard library:

```
python -m unittest discover -s tests -t .
```

# Actor input Schema

## `shopDomains` (type: `array`):

Public https storefront domains (up to 20). Only the domain is used; paths are ignored. Use shops you are permitted to analyze. IP addresses, custom ports and non-https URLs are rejected.

## `maxProductsPerShop` (type: `integer`):

Upper bound on products read from each shop's public feed (10-1000). Larger samples cost more time but describe the catalog better.

## `priceBandEdges` (type: `array`):

Boundaries in the shop's own price units, e.g. 25, 50, 100. A product at exactly an edge goes to the higher band. Leave empty for automatic quartile bands (needs at least 8 priced products).

## `topN` (type: `integer`):

How many of the most frequent product types and vendors to list per shop; the rest are summed in otherCount.

## `requestDelaySeconds` (type: `number`):

Pause before every request to the same shop. Minimum 0.5.

## Actor input object example

```json
{
  "shopDomains": [
    "allbirds.com"
  ],
  "maxProductsPerShop": 250,
  "priceBandEdges": [],
  "topN": 10,
  "requestDelaySeconds": 1
}
```

# 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 = {
    "shopDomains": [
        "allbirds.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-shopify-category-price-bands").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 = { "shopDomains": ["allbirds.com"] }

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-shopify-category-price-bands").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 '{
  "shopDomains": [
    "allbirds.com"
  ]
}' |
apify call practical_ai_lab_jp/pal-shopify-category-price-bands --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-shopify-category-price-bands"
        }
    }
}
```

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/Sk1BBPdw822wGvnT2/builds/uarFH24IHLxNUDm4p/openapi.json
