E-commerce data

Shopify Products Catalog Scraper: prices, variants, stock, bulk

Product catalogue of any Shopify store from its public products feed, up to 200 stores a run and 5,000 products each: title, handle, URL, vendor, type, tags, dates, price range, sale and stock flags, every variant with SKU, price and options, images and description. Robots-aware. Pay per product.

$1.00per 1,000 · Product listed

One product delivered with its variants. Stores that are not on Shopify, closed by robots.txt or without products are never charged.

Call it from your code

curl
ACTOR=steadydata~shopify-products-catalog
URL="https://api.apify.com/v2/acts/$ACTOR/run-sync-get-dataset-items"

curl -X POST "$URL?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"stores": ["allbirds.com", "gymshark.com"], "maxProductsPerStore": 500, "includeDescription": false}'
python
import os, requests

ACTOR = "steadydata~shopify-products-catalog"
URL = f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items"

rows = requests.post(
    URL,
    params={"token": os.environ["APIFY_TOKEN"]},
    json={'stores': ['allbirds.com', 'gymshark.com'], 'maxProductsPerStore': 500, 'includeDescription': False},
    timeout=900,
).json()

# every row carries a status; failures are records, not exceptions
ok = [r for r in rows if r.get("status") == "ok"]
print(len(ok), "rows delivered")
javascript
const ACTOR = "steadydata~shopify-products-catalog";
const url =
  `https://api.apify.com/v2/acts/${ACTOR}/run-sync-get-dataset-items` +
  `?token=${process.env.APIFY_TOKEN}`;

const rows = await fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({"stores": ["allbirds.com", "gymshark.com"], "maxProductsPerStore": 500, "includeDescription": false}),
}).then((r) => r.json());

// one object per delivered row, same shape every time
console.log(rows.filter((r) => r.status === "ok").length, "rows");

Use it in an agent

Point an MCP client at Apify's hosted server with this dataset in the tools list, or run our own server locally. The field explanations in the table below travel with the schema, so your agent reads them straight from the platform instead of from this page.

mcp url
https://mcp.apify.com?tools=steadydata/shopify-products-catalog

# the tool arrives in your agent as steadydata--shopify-products-catalog

Input

FieldTypeNameWhat it does
stores
required
array of stringShopify storesOne per row, up to 200: the store domain or any URL on it (allbirds.com, https://gymshark.com/collections/all).
maxProductsPerStoreintegerMax products per storeCost ceiling per store, in the feed's order (newest first). Pages of 250.
includeDescriptionbooleanInclude the product descriptionOn: the description as plain text. Off keeps rows small.
onlyAvailablebooleanOnly products with stockOn: products where no variant is available are skipped and not charged.

What you get back

output schema, one object per delivered row
{
  "store": string | null,
  "productId": string | null,
  "title": string | null,
  "handle": string | null,
  "url": string | null,
  "vendor": string | null,
  "productType": string | null,
  "tags": array | null,
  "createdAt": string | null,
  "updatedAt": string | null,
  "publishedAt": string | null,
  "currency": string | null,
  "priceMin": number | null,
  "priceMax": number | null,
  "compareAtPriceMax": number | null,
  "onSale": boolean | null,
  "available": boolean | null,
  "variantCount": integer | null,
  "availableVariantCount": integer | null,
  "variants": array | null,
  "optionNames": array | null,
  "imageCount": integer | null,
  "imageUrl": string | null,
  "images": array | null,
  "description": string | null,
  "status": string | null,
  "input": string | null,
  "errorCode": string | null,
  "error": string | null
}
FieldTypeWhen it is filled
storestring, null
productIdstring, null
titlestring, null
handlestring, nullThe shop's own URL slug for the product, stable across renames.
urlstring, null
vendorstring, nullFilled when the shop fills in a vendor; it is an optional field in Shopify.
productTypestring, nullFilled when the shop fills in a product type; it is an optional field in Shopify.
tagsarray, nullFilled when the shop tags its products; many shops use collections only.
createdAtstring, nullWhen the product was created in the shop, in UTC.
updatedAtstring, nullWhen the product was last changed in the shop, in UTC.
publishedAtstring, nullWhen the product became visible in the shop; empty for a product that is not published.
currencystring, nullThe shop currency. Shopify publishes one currency per shop in this feed.
priceMinnumber, nullThe lowest variant price; for a product with one variant the same as priceMax.
priceMaxnumber, nullThe highest variant price; for a product with one variant the same as priceMin.
compareAtPriceMaxnumber, nullThe crossed-out price, filled when the shop set one; empty when the product is not discounted.
onSaleboolean, nullTrue when the crossed-out price is higher than the asking price.
availableboolean, null
variantCountinteger, nullHow many variants the product has, for example sizes or colours.
availableVariantCountinteger, nullHow many of those variants are in stock right now.
variantsarray, null
optionNamesarray, nullWhat the variants vary on, for example Size and Colour.
imageCountinteger, null
imageUrlstring, nullThe first image; images holds all of them.
imagesarray, null
descriptionstring, nullFilled when includeDescription is on; off by default because it makes rows much larger.
statusstring, nullEither 'ok' or 'error'. An error row carries errorCode and error, and leaves the data fields empty; it is never charged.
inputstring, nullThe input this row was built from, so a row can always be traced back.
errorCodestring, nullFilled on an error row only; a delivered row leaves it empty.
errorstring, nullFilled on an error row only; a delivered row leaves it empty.

Related datasets

Already using this one? Ratings are the first thing other buyers look at, and this dataset has none yet. If it does a job for you, a rating on its Apify page is the one thing that helps. It takes a minute and it is the only thing we ask for.

Open the Apify page