# Gap US Scraper - Clothing, Prices, Discounts & Stock (`abotapi/gap-us-shopping-scraper`) Actor

Scrape Gap US (gap.com) clothing and fashion products by category. Extract product names, prices, discounts, ratings, stock availability, colors, color-specific details and full descriptions. Ideal for price monitoring, competitor analysis and retail research.

- **URL**: https://apify.com/abotapi/gap-us-shopping-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 product results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Gap, Old Navy, Banana Republic & Athleta Scraper: Products, Prices, Stock & Reviews

This scraper turns the Gap Inc online stores (Gap, Old Navy, Banana Republic, Athleta, Gap Factory and Banana Republic Factory) into a clean product data API. Search by keyword, pick categories by name, or paste product, category, keyword-search and sitemap links and scrape them directly. Every product comes back as one row with price range, was-price, discount, rating, review count and every colour with its own price and stock, plus (on demand) the full description, fit and fabric text, every size with its barcode and stock, breadcrumbs, the rating breakdown and customer reviews. Export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Six storefronts, one input.** Tick any mix of Gap, Old Navy, Banana Republic, Athleta, Gap Factory and Banana Republic Factory; every keyword and category runs in each, and pasted links go to the store they belong to.
- **Up to 200 colour listings per request, at no extra cost.** Category and search pages already carry price, was-price, discount, rating, review count, every colour with its price, stock count and image, so a plain run is the cheapest way to watch a whole department.
- **Prices per colour, filters at the source.** The same style can be priced differently per colour: the row gives the lowest and highest price and `colors` gives each colour's own price, was-price, discount, price type and stock. Price window, on-sale, minimum rating and sort order are applied by the store, and the filters you tick on the site (written after the `#` in a category link) are applied too.
- **Full detail on demand.** Product pages add the description, fit and size notes, fabric composition and care, merchandise department, size chart link, breadcrumbs, colour galleries and videos, and a size table with a barcode, stock status and price for every colour and size.
- **Customer voice included.** Review text, stars, title, date, public display name, helpful votes, the verified-purchase flag and how the item fit, in the order you choose, plus the rating breakdown and recommended share, optionally with the photos reviewers attached.
- **Built for monitoring.** Incremental mode returns only new, changed and, optionally, delisted products on a schedule, a price-only mode keeps description edits out of your bill, and a refused run fails loudly instead of returning an empty dataset.
- **Honest about gaps.** A product page that no longer exists is skipped and never billed, and stock counts are kept out of change detection unless you ask for them, because they move with every order.

### Use Cases

- **Price and markdown monitoring:** track price drops, clearance moves and was-prices across a department, a brand or a product list on a schedule.
- **Competitive intelligence for retailers and brands:** compare assortment, pricing, colour depth, size ranges and ratings across the six storefronts.
- **Size and availability analysis:** see which sizes and colours are in stock, with barcodes to match your own catalogue.
- **Review mining:** pull written reviews with stars, dates, fit feedback and helpful votes to find what customers love and what breaks.
- **Catalogue and content teams:** build a structured feed of names, copy, fabric, fit notes, images and barcodes for a comparison site or app.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `productKey` / `productId` | `"oldnavy:100001"` (the row's identity) / `"100001"` (the style number in the product link) |
| `name` / `brand` / `brandCode` | `"Sample Crew Sweater"` / `"Old Navy"` / `"oldnavy"` |
| `url` | link to this product, without tracking parameters |
| `price` / `priceMax` / `regularPrice` / `currency` | `22.49` / `29.99` / `44.99` / `"USD"` (lowest and highest colour price, full price of the cheapest colour) |
| `discountPercent` / `discountAmount` / `onSale` / `priceType` | `50` / `22.5` / `true` / `"Sale"` |
| `rating` / `reviewCount` | `4.5` / `62` |
| `colors` | one entry per colour: `colorId`, `colorName`, `colorGroup`, its own `price`, `regularPrice`, `discountPercent`, `priceType`, `stockStatus`, `inventoryCount`, `imageUrl`, `badges`, `promoFlags`; with a product page read also `season`, `storePickup`, `onlineExclusive`, `finalSale`, `swatchUrl`, `images`, `videos` |
| `inStock` / `totalInventory` | at least one colour in stock / units across colours (result pages) |
| `productType` / `categoryId` / `categoryName` | `"womens sweaters"` / `"1185233"` / `"Shop All Women's"` |
| `promoFlags` / `badges` / `freeShipping` / `excludedFromPromotion` | promotion lines, badges such as New, shipping and promotion flags |
| `description` / `details` / `highlights` | the product copy and its bullet points (needs a product page read) |
| `fitAndSizing` / `materials` / `care` | fit and size notes, fabric composition, care and origin lines (product page) |
| `sizes` / `fitVariants` / `skus` | every size in the store's order, fits such as Regular, Tall and Petite, and one `skus` entry per colour and size with `sku`, `size`, `size2`, `fit`, `stockStatus`, `inStock`, `upc`, `price` and `regularPrice` (product page) |
| `department` / `division` / `productClass` / `productSubclass` | the store's merchandise hierarchy (product page) |
| `breadcrumbs` / `sizeGuideUrl` / `maxOrderQuantity` | navigation path, size chart link and order limit (product page) |
| `ratingHistogram` / `recommendedPercent` / `fitSummary` | review count per star, share of reviewers who recommend, average fit by measure (product page) |
| `reviews` | written reviews (with **Collect customer reviews**) |
| `imageUrl` | front image of the first colour |
| `position` / `page` / `sourceLabel` | where the product ranked in its walk and what it was found through |
| `changeType` / `changedFields` / `firstSeenAt` / `lastSeenAt` | incremental mode: `"NEW"` / `[]` / timestamps |

Every record also carries `recordType`, `colorCount`, `reviewsRequested`, `reviewsDelivered`, `sourceUrl` and `scrapedAt`.

### How to Use

1. Pick a **mode**: `search` (keywords), `category` (categories by name) or `url` (paste links).
2. Tick the **storefronts** to search or browse, fill in the fields for that mode, then apply any sort, price, rating or sale filters you want.
3. Set **Max products** to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search two storefronts by keyword, cheapest first:**

```json
{
  "mode": "search",
  "brands": ["oldnavy", "gap"],
  "queries": ["denim jacket", "cargo pants"],
  "sortBy": "priceAsc",
  "maxItems": 100
}
```

**Read a category by name, marked-down products only:**

```json
{
  "mode": "category",
  "brands": ["athleta"],
  "categoryKeywords": ["leggings"],
  "maxCategories": 2,
  "onSaleOnly": true,
  "maxItems": 100
}
```

If you leave `urls` untouched in url mode, the actor scrapes five sample links, one per link shape. Replace them with your own links.

**Paste links, with full product-page detail and reviews:**

```json
{
  "mode": "url",
  "urls": [
    "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233#department=136&priceType=P",
    "https://www.gap.com/browse/product.do?pid=1184537",
    "845114"
  ],
  "brands": ["athleta"],
  "fetchDetails": true,
  "fetchReviews": true,
  "maxReviewsPerProduct": 50,
  "maxItems": 40
}
```

**Monitor a category on a schedule, price changes only:**

```json
{
  "mode": "url",
  "urls": ["https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233"],
  "incrementalMode": true,
  "changeScope": "priceAndStock",
  "emitExpired": true,
  "expireAfterMissedScans": 2,
  "maxItems": 0
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/gap-us-shopping-scraper").call(run_input={"mode": "search", "brands": ["oldnavy"], "queries": ["sweater"], "maxItems": 50})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["name"], product["price"], product["rating"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/gap-us-shopping-scraper').call({ mode: 'search', brands: ['oldnavy'], queries: ['sweater'], maxItems: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Link shapes you can paste

In url mode the actor reads these shapes from any of the six storefronts: a product page (`/browse/product.do?pid=<number>`, a colour number is read as its style, tracking parameters are dropped) or a bare product number (read in the first ticked storefront), a category page (`/browse/<department>/<name>?cid=<number>` or the older `/browse/category.do?cid=<number>`), a category page carrying the filters you ticked on the site (everything after the `#`, for example `#department=136&priceType=P&sortByField=price&sortByDir=asc`, which is applied exactly as the site applies it), a keyword-search link (`/browse/search.do?searchText=<keyword>`, which is read as that keyword in that storefront), and a category sitemap (`/native-category-sitemap.xml`, whose categories are walked, broadest first). The site's own "New", "Best Sellers" and "Featured" orders are not offered by the data feed and are ignored; price and rating orders are kept. A link to a home page, an account, cart or checkout page is skipped with a warning that names the reason.

#### Search mode

Each entry in `queries` is searched in every ticked storefront (up to 50 different keywords per run, repeats ignored) and its result pages are read one after another to the last page the store offers, up to 200 colour listings per page, until **Max products** is reached. **Max products** is shared evenly between the keyword and storefront pairs, and a product found twice is returned once. A style that has several colours appears on more than one result page; the actor returns it once.

#### Category mode

Category mode looks up categories whose name holds your words (`categoryKeywords`, at most `maxCategories` per storefront, broadest names first) in each ticked storefront's public category list and navigation, and walks their result pages. A category matches when its address contains every word of at least one phrase; a word also matches its plural (dress, dresses). **Max products** is shared evenly between the categories.

#### Output tab views

The Output tab offers six views: **Products** (price, discount, rating, colours and stock), **Product detail** (copy, fit and fabric text, sizes, breadcrumbs), **Colours** (one row per colour with its own price, discount and stock), **Sizes and barcodes** (one row per colour and size), **Customer reviews** (one row per written review) and **Changes since the last run** (incremental status and changed fields; with Compact updates on, also the old and new values of each price change and the per-colour changes). In the Colours, Sizes and Reviews views the colour's, size's or review's own value (price, stock, stars) replaces the product's in that column. Customer reviews are capped per product by **Max reviews per product**, from 0 to 1000 (default 20).

#### Keeping a run cheap

A search or category page already carries price, was-price, discount, rating, every colour with its price and stock, and images for many products, at no extra cost. **Read each product's page** and **Collect customer reviews** each need one extra page per product and are billed with one Product page detail event per product that reaches the dataset. They share that one page, so turning both on costs the same as turning on either. Each review returned inside a row is billed with one Customer review event. A pasted product link always reads the product page, because there is no result card behind it, so it is billed one Product page detail event whenever a row reaches the dataset, whatever the toggle says. The price, sale and rating filters are sent to the store, so a narrowed run reads fewer pages; the actor re-checks every returned product against them.

#### How Max products and the filters shape a run

When **Max products** is set, every category, keyword and link group in a run gets its own share of it, split evenly, so one large category cannot use up the whole run and starve the rest. A style passes a price limit when any of its colours is inside it. The store sorts by its own price and rating measures, so an order such as price low to high is broad rather than strict from one row to the next.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same scope. Each product is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED` or `EXPIRED` (only after a run that scanned the whole tracked scope, and only with `emitExpired`). `stateKey` names or shares the stored state. With Incremental mode off, output is exactly as before.

**What counts as a change.** Compared: price range, was-price, discount, on-sale flag, price type, name, brand, description, fit and fabric text, every colour with its own price, and the size table. Excluded by default but still returned in the row: scrape timestamps, the product's rank and page, where it was found (link, keyword, category), ratings and reviews (average rating, review count, rating breakdown, fit summary and the review sample, which move with every new review), the rotating promotion and new-arrival flags, and image and video links. Stock and availability (`inStock`, `totalInventory`, per-colour stock and unit counts, per-size stock) are excluded too, because they move with every order; turn on **Count stock changes** (`watchStock`) only if you accept that those moves mark products `UPDATED`. Colour and size lists are compared regardless of the order the store returns them. Switching the exclusions never causes a one-time wave of `UPDATED` rows: `UNCHANGED` is decided by re-reading the stored record under the current settings.

- `changeScope: priceAndStock` compares only price, was-price, discount, on-sale flag and per-colour price (and stock, when counted), so a name, description or fit-text edit is never a billable `UPDATED` row. Switching `changeScope`, or editing `ignoreFieldsForChanges`, keeps your existing saved state: nothing comes back as `NEW`, each stored product is simply re-compared under the new setting.
- `compactUpdates` turns `UPDATED` and `EXPIRED` rows into slim rows (ids, link, name, current price and stock, `priceChanges` old and new, `variantChanges` per colour as `ADDED`, `REMOVED` or `CHANGED`). `NEW` rows stay full. A compact row does not carry product-page detail fields or reviews, so no detail or review charge applies to it.
- `expireAfterMissedScans` applies with `emitExpired`. `EXPIRED` means the product dropped out of the scanned results or its page is gone; categories rotate products in and out as the stores restock, so a value of 2 or more avoids false expiries.
- A run stopped by **Max products**, **Max result pages**, the time limit or your maximum cost per run, a run that could not read some page, a resumed run, or a run whose scope or filters differ from those the state key was last saved with never produces `EXPIRED` rows.

`EXPIRED` rows count against **Max products** like every other row. They are only produced after a run that scanned the whole tracked scope, so a run that stopped because it reached **Max products** returns none. Products held back that way stay tracked and are returned, and billed, as `EXPIRED` on a later complete run.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each product, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` to search by keyword, `category` to pick categories by name, or `url` to scrape pasted links. |
| `brands` | array | `["gap"]` | Storefronts to search or browse: `gap`, `oldnavy`, `bananarepublic`, `athleta`, `gapfactory`, `bananarepublicfactory`. |
| `queries` | array | `["denim jacket"]` | Keywords to search (search mode), up to 50 different ones. |
| `categoryKeywords` | array | `["women dresses"]` | Words in the category name (category mode). |
| `maxCategories` | integer | `5` | How many matching categories are walked per storefront (category mode). |
| `urls` | array | five sample links (one per link shape) | Product, category, keyword-search or sitemap links, or bare product numbers (url mode). |
| `sortBy` | string | `relevance` | `relevance`, `priceAsc`, `priceDesc` or `ratingDesc` for search and category pages. A sort written into a pasted link wins. |
| `minPrice` | number | (none) | Only keep products with a colour at or above this price, in US dollars. |
| `maxPrice` | number | (none) | Only keep products with a colour at or below this price, in US dollars. |
| `onSaleOnly` | boolean | `false` | Keep only products with a colour on sale or clearance. |
| `minRating` | number | (none) | Keep only products with an average rating of at least this value (0 to 5). |
| `fetchDetails` | boolean | `false` | Search and category walks: read every product's own page for the full description, fit text, sizes and barcodes. |
| `fetchReviews` | boolean | `false` | Also collect customer reviews (reads the product page too). |
| `maxReviewsPerProduct` | integer | `20` | Cap on reviews collected per product, from 0 to 1000. |
| `reviewSort` | string | `newest` | `newest`, `oldest`, `helpful`, `highest` or `lowest`. |
| `includeReviewPhotos` | boolean | `false` | Add the photo links reviewers attached. |
| `maxItems` | integer | `20` | Maximum number of products to return across the whole run, up to 100000. `0` = the 100000 maximum. |
| `maxPages` | integer | `0` | Maximum result pages per keyword or category. `0` = no limit set; a safety limit always applies, and the log says when a limit stops a keyword or category that still had results. |
| `proxy` | object | US residential | Connection settings. |
| `resumeFromRunId` | string | (none) | Continue one interrupted run without re-collecting or re-billing its products. |
| `incrementalMode` | boolean | `false` | Return only new and changed products on scheduled runs. |
| `stateKey` | string | (none) | Name or share an incremental-mode state. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) unchanged products. |
| `emitExpired` | boolean | `false` | Also return (and bill) products no longer found after a full scan, within the room left under **Max products**. |
| `changeScope` | string | `all` | `priceAndStock` limits change detection to price (and stock, when counted) and per-colour price. |
| `watchStock` | boolean | `false` | Count stock and availability changes as changes. |
| `compactUpdates` | boolean | `false` | Slim `UPDATED` and `EXPIRED` rows with `priceChanges` and `variantChanges`. |
| `expireAfterMissedScans` | integer | `1` | Mark `EXPIRED` only after this many consecutive complete scans missed the product. |
| `ignoreFieldsForChanges` | array | (none) | Extra fields (or dotted paths such as `colors.price`) that never count as a change. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each product to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which products are created. |
| `maxNotifyListings` | integer | `50` | Cap on products written to each connector per run. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "recordType": "product",
  "productKey": "oldnavy:100001",
  "productId": "100001",
  "name": "Sample Crew Sweater",
  "brand": "Old Navy",
  "brandCode": "oldnavy",
  "url": "https://oldnavy.gap.com/browse/product.do?pid=100001",
  "currency": "USD",
  "price": 22.49,
  "priceMax": 29.99,
  "regularPrice": 44.99,
  "discountPercent": 50,
  "discountAmount": 22.5,
  "onSale": true,
  "priceType": "Sale",
  "rating": 4.5,
  "reviewCount": 62,
  "colorCount": 2,
  "colors": [
    {"colorId": "100001002", "colorName": "Black Jack", "colorGroup": "Black", "price": 22.49, "regularPrice": 44.99, "discountPercent": 50, "priceType": "Sale", "stockStatus": "In Stock", "inventoryCount": 120, "imageUrl": "https://content.gapinc.com/b/0000/000/000/sample.jpg", "promoFlags": ["4 Days Only Deal"]},
    {"colorId": "100001012", "colorName": "Clove", "colorGroup": "Red", "price": 29.99, "regularPrice": 44.99, "discountPercent": 33, "priceType": "Sale", "stockStatus": "In Stock", "inventoryCount": 80}
  ],
  "inStock": true,
  "totalInventory": 200,
  "productType": "womens sweaters",
  "categoryId": "1185233",
  "categoryName": "Shop All Women's",
  "badges": ["New"],
  "description": "Soft knit crew-neck sweater. Ribbed trim.",
  "fitAndSizing": ["Classic fit.", "Hits at hip."],
  "materials": ["53% Cotton, 24% Nylon, 23% Acrylic."],
  "care": ["Machine wash cold.", "Imported."],
  "sizes": ["XS", "S", "M", "L", "XL"],
  "fitVariants": ["Regular", "Tall", "Petite"],
  "skus": [
    {"sku": "10000100200009", "colorId": "100001002", "fit": "Regular", "size": "XS", "stockStatus": "IN_STOCK", "inStock": true, "upc": "000000000001", "price": 22.49, "regularPrice": 44.99}
  ],
  "breadcrumbs": [{"name": "Women", "url": "https://oldnavy.gap.com/browse/women?cid=1185233"}],
  "ratingHistogram": {"1": 3, "2": 2, "3": 5, "4": 12, "5": 40},
  "recommendedPercent": 91,
  "fitSummary": {"Overall Size": "true to size"},
  "position": 3,
  "page": 1,
  "sourceLabel": "women/shop-all-womens",
  "reviewsRequested": true,
  "reviews": [
    {"reviewId": "1", "rating": 5, "title": "So soft", "text": "Sample review text.", "author": "Sam K", "date": "2026-09-25T10:00:00.000+00:00", "recommended": true, "helpfulVotes": 2, "notHelpfulVotes": 0, "verifiedPurchase": true, "colorId": "100001002", "fit": {"Overall Size": "true to size"}}
  ],
  "reviewsDelivered": 1,
  "changeType": "NEW",
  "changedFields": [],
  "firstSeenAt": "2026-10-31T12:00:00Z",
  "lastSeenAt": "2026-10-31T12:00:00Z",
  "scrapedAt": "2026-10-31T12:00:00Z"
}
```

### Plan Requirement

These stores refuse most requests from ordinary datacenter addresses and serve US residential addresses reliably, so the default connection is Apify's US residential proxy; leave it as it is for the most reliable results. If a page is refused, the run switches to a fresh connection and another client profile and retries. If refusals keep coming it escalates, in order, to residential exits (when you picked a datacenter pool), backup connections and finally Apify Proxy's enhanced group, and it stays on the connection that works for the rest of the run. Proxy traffic is billed on your own Apify plan, and the enhanced group costs more per request.

Apify free plans do not include residential proxies. If your plan does not include proxy access, the run says so in plain words rather than returning an empty dataset. These are the messages you may see in the run log:

- Warning: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." followed by an upgrade pointer.
- Info: "Served over the backup connection (the default connection was unavailable)."
- Failure: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required".
- Failure: "No results: residential proxies are unavailable on this run (Apify free plans do not include them) and these stores refuse ordinary datacenter addresses."
- Failure: "Could not load any page (the requests failed), so nothing was collected. This is a connection problem, not an empty result. Please try again later."

You can supply your own proxy URLs instead; custom proxies get no automatic backup connection.

### FAQ

#### How much does it cost?

Four events can appear on a run, and the **Pricing** tab shows the current rates:

- **Dataset item:** one event per product row returned in the dataset (incremental `UNCHANGED` rows are suppressed and not billed unless **Emit unchanged** is on).
- **Product page detail:** one event per product row whose own page is read, whether through **Read each product's page**, **Collect customer reviews** or a pasted product link. Turning both toggles on still costs one event per product.
- **Customer review:** one event per review returned inside a product row.
- **Actor start:** one event each time a run starts.

Use **Max products** to cap the cost of any run. Failed, empty or missing pages are never billed, and a run that reaches your maximum cost per run stops and says so.

#### Is it legal to scrape these stores?

This actor collects only publicly available product data from pages and feeds the stores publish for visitors. You are responsible for how you use it: follow the stores' terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices and specifications are generally facts, but product photos and brand names may be subject to third-party rights. Reviews are written by private individuals, so treat them as personal data where privacy law applies.

#### Which storefronts are covered, and which country?

The six US storefronts: Gap, Old Navy, Banana Republic, Athleta, Gap Factory and Banana Republic Factory, in US dollars. Connections are pinned to the United States because the stores redirect other countries to their local sites.

#### Can I search by keyword?

Yes. Use search mode and list one or more keywords; pick the storefronts to search. Category mode (words from the category name) remains for walking whole departments, and you can paste a category page you found on a store, with any filter you ticked.

#### Can I get only new or changed products on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated products, and unchanged ones are not billed unless you turn on **Emit unchanged**.

#### Why do some rows have no description or sizes?

Result pages carry prices, colours, stock counts and ratings, while the description, fit and fabric text, size table and breadcrumbs live on each product's own page. Turn on **Read each product's page**, or paste product links, to get them.

#### Why were some products skipped?

The stores list some product pages that no longer exist. A page is skipped, not returned and not billed, only after two fresh connections each received the store's own no-results page. The log reports how many were skipped.

#### Why did my run fail instead of returning an empty dataset?

If the stores refuse every request, the run stops with a clear message so "no matches today" is never confused with "nothing could be read". Run it again in a few minutes. If only some pages could be read, you still get what was found, and the run tells you the result is incomplete.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🛒 <a href="https://apify.com/abotapi/uniqlo-com-scraper"><b>UNIQLO Scraper</b></a><br>Scrape UNIQLO products across 21 country storefronts in local currency. Price with pre...</td><td>🛒 <a href="https://apify.com/abotapi/netshoes-scraper"><b>Netshoes Brazil</b></a><br>Scrape sportswear and footwear from netshoes.com.br. Search by keyword with the store's...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/muji-scraper"><b>MUJI Scraper</b></a><br>Scrape MUJI products from the United States, Canada and Australia storefronts. Search by...</td><td>🛒 <a href="https://apify.com/abotapi/kmart-au-scraper"><b>Kmart Scraper</b></a><br>Scrape products and customer reviews from Kmart.com.au. Search by keyword or use...</td></tr>
<tr><td>👗 <a href="https://apify.com/abotapi/mango-com-scraper"><b>Mango Scraper</b></a><br>Scrape Mango (shop.mango.com) fashion products: current price plus strike-through Rebajas...</td><td>🛒 <a href="https://apify.com/abotapi/zalando-scraper"><b>Zalando Scraper</b></a><br>Scrape Zalando products: name, brand, current and original price, discount, sizes...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/gap-us-shopping-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Choose 'search' to search by keyword, 'category' to pick category pages by name, or 'url' to scrape pasted links. Each mode reads only its own fields below.

## `brands` (type: `array`):

Which storefronts to search or browse. Gap is used when none is ticked. Each keyword and category is run in every ticked storefront. Pasted links ignore this setting, except a bare product number, which is read in the first ticked storefront.

## `queries` (type: `array`):

Words to search for, for example 'denim jacket'. One search per keyword and storefront; duplicates are searched once. Up to 50 keywords. Read only in search mode.

## `categoryKeywords` (type: `array`):

Words from the category's address, for example 'women dresses' or 'sweaters'. Several phrases select several categories. Read only in category mode.

## `maxCategories` (type: `integer`):

At most this many matching categories are walked in each ticked storefront (the broadest names are taken first).

## `urls` (type: `array`):

Storefront links to scrape directly. A category link may carry the filters the site writes after the # sign; they win over the price, sale and rating settings below. A product link always reads the product page. Read only in url mode.

## `sortBy` (type: `string`):

Order of the results in category and keyword scopes. A sort order written into a pasted category link wins. 'Relevance' keeps the store's own order.

## `minPrice` (type: `number`):

Only products with a colour priced at or above this amount. Leave empty for no lower limit.

## `maxPrice` (type: `number`):

Only products with a colour priced at or below this amount. Leave empty for no upper limit.

## `onSaleOnly` (type: `boolean`):

Only products with at least one colour marked down (sale or clearance price).

## `minRating` (type: `number`):

Only products rated at or above this average (0 to 5). Products without reviews are excluded.

## `fetchDetails` (type: `boolean`):

Search and category scopes only: read every product's own page for the full description, fit and fabric text, size and barcode table, breadcrumbs and rating breakdown. Off by default because it costs one extra page per product. Pasted product links always read the product page (there is no result card behind them), so those rows are always billed one 'Product page detail' event whatever this toggle says.

## `fetchReviews` (type: `boolean`):

Add customer reviews to every product row (this also reads the product page, so the detail surcharge applies). Billed per review returned.

## `maxReviewsPerProduct` (type: `integer`):

Reviews to collect per product when 'Collect customer reviews' is on. Up to 1000.

## `reviewSort` (type: `string`):

Which reviews come first when a product has more than the maximum.

## `includeReviewPhotos` (type: `boolean`):

Add the links of photos customers attached to their reviews.

## `maxItems` (type: `integer`):

Maximum number of products to return across the whole run, EXPIRED rows from Incremental mode included. This is the run's cap. Use 0 for the maximum of 100000 products per run.

## `maxPages` (type: `integer`):

Maximum number of result pages to take per keyword or category. Each page carries up to 200 colour listings (roughly 100 to 180 products). 0 = no page limit set; the run then stops at Max products, when there are no more results, or at a safety limit of 2000 pages per keyword or category (far above what Max products can reach). Product links have no result pages.

## `proxy` (type: `object`):

Connection used for every request. Leave the default (US residential); the run escalates on its own when the store refuses a page. Apify free plans do not include residential proxies.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large pull without returning or charging for products already collected there. Use this after an interrupted run. For recurring monitoring of the same scope, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns every matching product as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED products, which makes it a cheap way to watch price drops. What is compared: price range, was-price, discount, on-sale flag, price type, name, brand, description, fit and fabric text, every colour with its own price and the size table. What is excluded by default (still returned in the row, never a change): scrape timestamps, the product's rank and page, where it was found (link, keyword, category), ratings and reviews (average rating, review count, rating breakdown, fit summary and the review sample), the rotating promo and 'new' flags, and image links. Stock and availability (in-stock flag, units, per-colour and per-size stock) are also excluded unless you turn on 'Count stock changes', because they move with every order. Colour and size lists are compared regardless of the order the store returns them. 'Changes to watch' narrows this to price (and stock when counted) only. Turn on 'Emit unchanged' or 'Emit expired' only when you also want those rows returned (and billed). State is kept separately for each scope and filter setup; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to derive a key automatically from the scope and filter settings, so two different scopes never mix state.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return products that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you want a full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return products that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked scope without a page that could not be read, never when Max products capped it or Resume was used. A product page the store reports as not available also counts as missing; consider 'Expire after missed scans' above 1 so a brief gap does not expire a product. EXPIRED rows count against Max products: a run returns at most the room left under it, and the rest are returned on a later complete run. This returns, and bills, extra rows, so leave it off unless you need expiry tracking.

## `changeScope` (type: `string`):

All fields (default): any change to the compared product data marks a product UPDATED. Price only (and stock when counted): just price, was-price, discount, on-sale flag and per-colour price, so name, description or fit-text edits never produce a billable UPDATED row. Changing this never starts a new baseline: the existing saved state is kept (under an automatic or a named State key) and each stored product is simply compared under the new setting.

## `watchStock` (type: `boolean`):

Off by default. Stock figures move with every order, so counting them marks products UPDATED, and bills them, without a price or content change. Turn on to count the in-stock flag, unit totals and per-colour and per-size stock as changes.

## `compactUpdates` (type: `boolean`):

Off by default. When on, UPDATED and EXPIRED rows are slim: ids, link, name, current price and stock, plus priceChanges (old and new value of each field that moved) and variantChanges (per-colour ADDED, REMOVED or CHANGED). NEW rows stay full. A compact row does not carry product-page detail fields or reviews, so no detail or review charge applies to it.

## `expireAfterMissedScans` (type: `integer`):

Used with 'Emit expired'. A product is marked EXPIRED only after it was missing from this many CONSECUTIVE complete scans (default 1). Products rotate in and out of a category as the store restocks, so a value of 2 or more avoids false expiries.

## `ignoreFieldsForChanges` (type: `array`):

Field names that must not count as a change, for example name or highlights. Use a dotted path to ignore a key inside every element of a list, for example colors.price. Ignored fields are still returned in the row. Rank, where the product was found, ratings and reviews, promo flags and image links are already ignored by default. Changing this list never starts a new baseline: the saved state is kept and each stored product is re-compared under the new list, so no product comes back as NEW.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per product; other connectors get a best-effort write or digest. Each connector receives a condensed summary per product, not the full record; the complete record always stays in the dataset. Leave empty to skip; this never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which product pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on products written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "brands": [
    "gap"
  ],
  "queries": [
    "denim jacket"
  ],
  "categoryKeywords": [
    "women dresses"
  ],
  "maxCategories": 5,
  "urls": [
    "https://www.gap.com/browse/product.do?pid=1184537",
    "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233",
    "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233#department=136&priceType=P&sortByField=price&sortByDir=asc",
    "https://athleta.gap.com/browse/search.do?searchText=leggings",
    "https://bananarepublic.gap.com/native-category-sitemap.xml"
  ],
  "sortBy": "relevance",
  "onSaleOnly": false,
  "fetchDetails": false,
  "fetchReviews": false,
  "maxReviewsPerProduct": 20,
  "reviewSort": "newest",
  "includeReviewPhotos": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "changeScope": "all",
  "watchStock": false,
  "compactUpdates": false,
  "expireAfterMissedScans": 1,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `colors` (type: `string`):

No description

## `sizes` (type: `string`):

No description

## `reviews` (type: `string`):

No description

## `changes` (type: `string`):

No description

# 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 = {
    "mode": "search",
    "brands": [
        "gap"
    ],
    "queries": [
        "denim jacket"
    ],
    "categoryKeywords": [
        "women dresses"
    ],
    "maxCategories": 5,
    "urls": [
        "https://www.gap.com/browse/product.do?pid=1184537",
        "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233",
        "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233#department=136&priceType=P&sortByField=price&sortByDir=asc",
        "https://athleta.gap.com/browse/search.do?searchText=leggings",
        "https://bananarepublic.gap.com/native-category-sitemap.xml"
    ],
    "sortBy": "relevance",
    "onSaleOnly": false,
    "fetchDetails": false,
    "fetchReviews": false,
    "maxReviewsPerProduct": 20,
    "reviewSort": "newest",
    "includeReviewPhotos": false,
    "maxItems": 20,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "watchStock": false,
    "compactUpdates": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/gap-us-shopping-scraper").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 = {
    "mode": "search",
    "brands": ["gap"],
    "queries": ["denim jacket"],
    "categoryKeywords": ["women dresses"],
    "maxCategories": 5,
    "urls": [
        "https://www.gap.com/browse/product.do?pid=1184537",
        "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233",
        "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233#department=136&priceType=P&sortByField=price&sortByDir=asc",
        "https://athleta.gap.com/browse/search.do?searchText=leggings",
        "https://bananarepublic.gap.com/native-category-sitemap.xml",
    ],
    "sortBy": "relevance",
    "onSaleOnly": False,
    "fetchDetails": False,
    "fetchReviews": False,
    "maxReviewsPerProduct": 20,
    "reviewSort": "newest",
    "includeReviewPhotos": False,
    "maxItems": 20,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "watchStock": False,
    "compactUpdates": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/gap-us-shopping-scraper").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 '{
  "mode": "search",
  "brands": [
    "gap"
  ],
  "queries": [
    "denim jacket"
  ],
  "categoryKeywords": [
    "women dresses"
  ],
  "maxCategories": 5,
  "urls": [
    "https://www.gap.com/browse/product.do?pid=1184537",
    "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233",
    "https://oldnavy.gap.com/browse/women/shop-all-womens?cid=1185233#department=136&priceType=P&sortByField=price&sortByDir=asc",
    "https://athleta.gap.com/browse/search.do?searchText=leggings",
    "https://bananarepublic.gap.com/native-category-sitemap.xml"
  ],
  "sortBy": "relevance",
  "onSaleOnly": false,
  "fetchDetails": false,
  "fetchReviews": false,
  "maxReviewsPerProduct": 20,
  "reviewSort": "newest",
  "includeReviewPhotos": false,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "watchStock": false,
  "compactUpdates": false
}' |
apify call abotapi/gap-us-shopping-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/gap-us-shopping-scraper"
        }
    }
}
```

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/h1aGDXwzhb49CB2hF/builds/5mY4LUIEs9TfUulEv/openapi.json
