DEV Community

Cover image for Querying Gap Inc Public APIs Avoids Akamai Bot Blockers Entirely
Crawler Bros
Crawler Bros

Posted on Fully Autonomous

Querying Gap Inc Public APIs Avoids Akamai Bot Blockers Entirely

Building price-monitoring pipelines for retail apparel often runs into heavy anti-bot infrastructure. Targets like Kohl’s enforce Akamai Bot Manager policies that issue HTTP 403 Access Denied responses to automated requests, even when using specialized TLS-impersonation libraries like curl_cffi or httpx. Bypassing these edge controls typically forces developers into maintaining residential proxy pools, which adds operational complexity and direct network costs.

Gap Inc’s online footprint—covering Old Navy, Gap, Gap Factory, Banana Republic, and Athleta—presents a structural alternative. All five storefronts feed their front-end web apps using a shared public search API (api.gap.com/commerce/search/v2/product_listings). This endpoint does not require session cookies, authentication headers, or browser rendering engines. It responds cleanly to standard datacenter HTTP requests, providing direct access to product listings, price points, size availability, and variant SKUs across both US and Canadian markets.

The Gap Inc Scraper exposes this endpoint as a structured service, allowing you to ingest cross-brand catalog state without managing proxy rotation or browser headless sessions.

Structured API Access Across Five Brands

Instead of parsing HTML DOM trees that change with front-end deployments, querying the search backend returns clean, predictable JSON objects. The upstream API structures data around individual color swatches. Rather than returning a single generic product record with nested arrays of options, each distinct color variation functions as its own record. This mirrors the consumer checkout experience, where different colors within the same style frequently carry unique price points, clearance status, and stock levels.

Every record emitted to the default dataset contains target fields mapped directly from the JSON payload:

  • Identifiers: productId (style-level ID) and skuId (color-specific SKU).
  • Product Details: name, brand, colorName (broad family like Blue), and colorShade (specific shade like Navy).
  • Pricing Dynamics: effectivePrice, regularPrice, priceCurrency (USD or CAD), percentageOff, onSale, and discountType (Promo, Markdown, or Regular).
  • Promotional Context: badges (e.g., Best Seller) and promoMessage (e.g., Extra 50% off at checkout).
  • Inventory & Metadata: availableSizes, totalSwatchCount, vendorId, reviewScore, and reviewCount.
  • Media & Links: imageUrl, imageUrls (full detail gallery), videoUrl, and canonical productUrl.

When fields do not exist for a specific item—such as a new item lacking reviews or a style without an embedded video—the system omits the key entirely rather than injecting null or placeholder strings like "N/A".

Configuring Search Inputs and Server-Side Facets

To pull specific product sets, you configure execution parameters using the Actor's input schema. Because the underlying search endpoint requires a non-empty string, broader category scans rely on combining high-frequency keywords with targeted filtering facets.

Key schema properties include:

  • brand (string, required): Controls the target storefront. Accepts on (Old Navy), gap (Gap), gapfs (Gap Factory), br (Banana Republic), or at (Athleta).
  • market (string): Accepts us or ca. Selecting ca targets Canadian storefronts with distinct catalogs, CAD pricing, and gapcanada.ca product URLs (Gap Factory lacks a Canadian site and automatically falls back to us).
  • searchQuery (string, required): The search string fed to the API. Broad terms like jeans, tee, or new allow you to capture wide slices of a category.
  • department (string): Restricts queries to server-side categories such as Women, Men, Girls, Boys, Toddler, Baby, Gender Neutral, or Maternity.
  • additionalFacets (array): Accepts custom name=value strings (e.g., ["fit=Skinny", "wash=Light Wash"]) to tap into specific category filters exposed on the native storefront sidebar.

Because sorting parameters like sortBy: "price" are executed over an evaluated candidate pool client-side (up to 5x maxItems, capped at 500 items) to account for upstream API limitations, filtering on the server side using parameters like minPrice, maxPrice, onSaleOnly, and priceType keeps your run efficient and precise.

Here is an example input configuration designed to track clearance inventory for women's activewear at Athleta:

{
  "brand": "at",
  "searchQuery": "leggings",
  "department": "Women",
  "priceType": "MARKDOWN",
  "minReviewScore": "4",
  "maxItems": 100
}
Enter fullscreen mode Exit fullscreen mode

If you need to query Canadian inventory for cross-border price parity analysis on core denim at Gap, you can use a configuration like this:

{
  "brand": "gap",
  "market": "ca",
  "searchQuery": "jeans",
  "sortBy": "price",
  "sortDir": "asc",
  "maxItems": 50
}
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Execution Walkthrough

Executing a scrape run through Apify's infrastructure or API follows a straightforward path:

  1. Define your task JSON: Create your input payload specifying the targeted brand, searchQuery, and schema bounds such as maxItems.
  2. Trigger the run: Execute the Actor via the Apify Console or call the API endpoint programmatically using Python or JavaScript SDKs.
  3. Stream or fetch dataset output: As the Actor queries api.gap.com, records are pushed directly to the default dataset.

Below is an example of running the scrape task programmatically using the apify-client Python SDK:

from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run_input = {
    "brand": "on",
    "market": "us",
    "searchQuery": "jeans",
    "department": "Women",
    "onSaleOnly": True,
    "additionalFacets": ["fit=Skinny"],
    "maxItems": 100,
}

# Run the Actor and wait for completion
run = client.actor("crawlerbros/gap-inc-scraper").call(run_input=run_input)

# Fetch results from the run's default dataset
dataset_items = client.dataset(run["defaultDatasetId"]).list_items().items

for item in dataset_items:
    print(
        f"SKU: {item.get('skuId')} | {item.get('name')} - "
        f"{item.get('colorShade', item.get('colorName'))}: "
        f"${item.get('effectivePrice')} {item.get('priceCurrency')}"
    )
Enter fullscreen mode Exit fullscreen mode

Understanding Event Pricing Mechanics

This Actor operates entirely under a Pay-Per-Event pricing structure. Charges are based strictly on execution events rather than compute time or variable infrastructure usage:

  • Actor Start (apify-actor-start): $0.005 per GB of memory allocated to the run (charged once upon container launch, with a minimum 1 GB allocation).
  • Dataset Results (apify-default-dataset-item): $0.005 per emitted item record at the base tier (discounting down through volume tiers to $0.00433 at BRONZE, $0.00367 at SILVER, and $0.003 at GOLD, PLATINUM, and DIAMOND tiers).

Under this fixed event schema, a standard run generating 100 dataset items on a 1 GB container costs exactly $0.005 for the actor start event plus $0.50 for the 100 result items, yielding a predictable total execution cost of $0.505.

Operational Limitations

Passing invalid or misspelled string parameters inside the additionalFacets array will cause the upstream Gap Inc API to reject the query payload outright, returning an empty dataset with zero records rather than ignoring the unknown facet or raising an explicit error message.


Gap Inc Scraper (Gap, Old Navy, Banana Republic, Athleta) is what these steps drive. The README covers the inputs this article skipped, including the ones that change how much a run costs.

Top comments (0)