# SSENSE Scraper - Products, Sale & Retail Prices (`abotapi/ssense-product-scraper`) Actor

Scrape SSENSE by keyword, category, designer or URL. Every row carries the selling price and the regular price, the discount percent, stock state and images. Sale axes, designer pages and recurring change tracking are first-class.

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

## Pricing

from $1.70 / 1,000 product records

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

## SSENSE Product Scraper

Scrape products from SSENSE, the luxury fashion retailer: search by keyword,
browse the men's or women's catalogue, follow designers and categories, or
watch the sale section. Paste any store URL to walk it directly.

Every row carries the selling price **and** the regular (undiscounted) price,
with the discount percentage computed, so the retail-versus-sale pair is on
every record from the listing page itself. The product description is one
toggle away.

### What you get

| Field | Meaning |
|---|---|
| `recordId` / `productId` | The store's own product id, stable across runs |
| `title` / `brand` / `sku` | Product name, designer, style code |
| `price` | The current selling price |
| `regularPrice` | The regular (undiscounted) price |
| `onSale` / `discountPercent` | True when the selling price is below the regular price, and by how much |
| `currency` | The currency the row was quoted in |
| `inStock` | Availability as the store reports it on the page |
| `gender` / `category` / `categoryIds` | Section, product type keyword and the store's category ids |
| `imageUrl` / `url` | Product image and product page |
| `description` | Product description (with Fetch product details on) |
| `changeType` / `changedFields` / `firstSeenAt` / `lastSeenAt` | Incremental-mode change tracking fields |

### How to use it

**Search mode** (default): give it search terms and it walks the store's own
search results. Combine with the Section, Category, Designers, Sale only and
price filters; every filter narrows what the store itself serves, except the
price bounds, which are applied to the rows the run reads.

**URL mode**: paste any store URL. A product URL returns that single product
in full. A browse, category, designer or sale URL is walked page by page, and
the pasted URL decides its own scope: the price and ordering filters still
narrow what the run keeps, while the category, designers and sale only
filters are search mode only. Add `?page=3` to a browse URL to start from
that page.

**The sale axis** is a first-class facet: turn on Sale only (or paste a sale
URL) and the run rides the store's own sale pages, including their category
and designer combinations. Combined with Incremental mode this makes a plain
daily sale watch: first run returns everything as NEW, later runs return only
what changed, and a price move is reported as UPDATED.

#### Notes on scope

- A search term is never combined with a designer facet in one request, the
  store refuses that combination; a designer scope walks the designer's full
  page instead.
- A designer slug the store does not know would silently redirect to the full
  catalogue. The actor detects that redirect, refuses the page, and fails the
  run loudly rather than returning a wider scope than you asked for.
- Prices are quoted in the currency of the country the connection exits from;
  every row states its own currency.

### Output

Dataset rows are flat JSON. Example (illustrative placeholder, not a real
product):

```json
{
  "recordId": "00000001",
  "rowType": "product",
  "productId": "00000001",
  "title": "Sample Name Wool Coat",
  "brand": "Sample Designer",
  "sku": "000000S00000",
  "price": 890,
  "regularPrice": 1290,
  "onSale": true,
  "discountPercent": 31.0,
  "currency": "USD",
  "inStock": true,
  "gender": "men",
  "category": "coats",
  "imageUrl": "https://example.com/images/sample-coat.jpg",
  "url": "https://www.example.com/en-us/men/product/sample-designer/sample-name-wool-coat/00000001",
  "scrapedAt": "2026-01-01T00:00:00Z"
}
```

With **Incremental mode** on, rows also carry `changeType` (NEW, UPDATED,
UNCHANGED, REAPPEARED, EXPIRED), `changedFields`, `firstSeenAt` and
`lastSeenAt`. **Resume from a previous run** continues one interrupted crawl
without returning or billing the records it already returned.

### Limits and costs

- `maxItems` is the single cap on a run (0 = unlimited). `maxPages` is an
  optional safety bound per source.
- **Fetch product details** adds the product description per record. It is a
  paid extra (the Product description event on the pricing tab); the price
  pair, stock state and images are on every row either way.
- **Emit unchanged** and **Emit expired** return, and bill, extra rows; leave
  them off unless you want full snapshots or expiry tracking.

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

Results can be piped into the apps you already use through Model Context
Protocol (MCP) connectors, as an optional side channel. This never changes
the dataset output.

- `mcpConnectors`: authorize a connector under Apify, Settings, API &
  Integrations, then select it here. Notion receives a page per record;
  other connectors receive a best-effort write or digest. Each connector
  gets a condensed summary per record; the complete record always stays in
  the dataset.
- `notionParentPageUrl`: the Notion page under which record pages are
  created. Required for the Notion connector, ignored by the others.
- `maxNotifyListings`: cap on records written to each connector per run.

### Compute

This actor reads pages over plain HTTP; 512 MB of memory is the default and
is plenty. A minimal run finishes in well under a minute.

# Actor input Schema

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

How to start the scrape. 'search' runs store searches. 'url' processes the store URLs you paste: a product URL is fetched as a single product, and a browse, category, designer or sale URL is paged like a search, its own path deciding the scope. The price and ordering filters below apply in BOTH modes; the scope filters (category, designers, sale only) apply in search mode only.

## `searchTerms` (type: `array`):

One or more things to search the store for, for example 'sneakers', 'wool coat', 'leather bag'. Each term is scraped independently. Search mode only: in URL mode the scope is read out of each pasted store URL instead. Leave this empty and the run browses the section, category or designer picked below, narrowed by the filters.

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

Paste store product URLs (https://www.ssense.com/en-us/men/product/designer/name/123456), browse URLs (https://www.ssense.com/en-us/men), category URLs (https://www.ssense.com/en-us/men/sneakers), designer URLs (https://www.ssense.com/en-us/men/designers/designer-name) or sale URLs (https://www.ssense.com/en-us/men/sale). Add ?page=3 to any browse URL to start walking from that page instead of the first. A pasted URL decides its own scope: the price and ordering filters below still narrow what the run keeps, the section filter fills in only when the URL does not name a section of its own, and the scope filters (category, designers, sale only) are search mode only. A pasted product URL is the one exception: it returns that single product in full.

## `gender` (type: `string`):

Which storefront section to browse. Applies in BOTH search mode and URL mode: search mode sends it with every request, and in URL mode it fills in only when a pasted URL does not name a section of its own.

## `category` (type: `string`):

Only browse this category, as the slug the store itself uses in the URL, for example 'sneakers', 'coats', 't-shirts', 'bags'. The store has around a hundred of these; the slug is the part of a category page URL right after the section. Leave empty for every category. Search mode ONLY: in URL mode the pasted URL decides its own scope.

## `designerSlugs` (type: `array`):

Only browse these designers, as the slug the store itself uses in the URL, for example 'balenciaga', 'rick-owens', 'the-north-face'. The slug is the part of a designer page URL after /designers/. Each designer is scraped as its own scope; a search term is never combined with a designer in one request, because the store refuses that combination. Search mode ONLY: in URL mode the pasted URL decides its own scope.

## `saleOnly` (type: `boolean`):

Browse the sale section only, which is the store's own sale axis (the same pages its sale menu links to). Search mode ONLY: in URL mode the pasted URL decides its own scope, so paste a sale URL to walk the sale section there.

## `minPriceUsd` (type: `integer`):

Only keep rows whose selling price is at least this many US dollars. The store has no server side filter for this, so it is applied to the rows this run reads. Applies in BOTH search mode and URL mode.

## `maxPriceUsd` (type: `integer`):

Only keep rows whose selling price is at most this many US dollars. The store has no server side filter for this, so it is applied to the rows this run reads. Applies in BOTH search mode and URL mode.

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

Only keep rows whose selling price is below the regular price, that is rows with a discount. The store has no server side parameter for this outside its sale section, so it is applied to the rows this run reads. For full sale coverage use Sale only above. Applies in BOTH search mode and URL mode.

## `sortResultsBy` (type: `string`):

Arranges the rows this run collected, after they are read. This is a local tidy up of one run's output, not a catalogue wide ordering. Applies in BOTH search mode and URL mode.

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

Fetch each item's product page for the description and the product type keyword. Slower, and charged once per record. Leave off for a fast crawl: identity, prices, discount, stock state and images are included either way.

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

The single cap on this run: stop after collecting this many records across all search terms, designers and URLs. Set 0 for unlimited.

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

Safety bound on how many result pages to walk per search term, designer or URL. Leave empty (0) for no page limit: the run then stops at Max items, not here. Does not impose a separate cap below Max items.

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

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

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

Turn this on for daily or weekly recurring monitoring of the same search, for example a sale watch. The first run returns everything matching as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED records. Price moves ARE reported as UPDATED: on a store they are the signal, not noise. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately per search, URL 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 let the actor derive a key automatically from the search terms, URLs and filters, so two different searches never mix state.

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

Off by default. Turn on to also return records 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 specifically want a full snapshot every run.

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

Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search: not when Max items capped it, not when Resume was used, and not when the run was refused. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.

## `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 record; other connectors get a best-effort write or digest. Each connector receives a condensed summary per record, 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 record pages are created. Required to enable the Notion export; ignored by other connectors.

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

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

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

Leave the default for the cheapest run. Change it only if a run reports that it could not read any results. Prices are quoted in the currency of the country the connection exits from; every row states its own currency.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerms": [
    "sneakers"
  ],
  "urls": [
    "https://www.ssense.com/en-us/men/sneakers"
  ],
  "gender": "men",
  "saleOnly": false,
  "onSaleOnly": false,
  "sortResultsBy": "site_order",
  "fetchDetails": true,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (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",
    "searchTerms": [
        "sneakers"
    ],
    "urls": [
        "https://www.ssense.com/en-us/men/sneakers"
    ],
    "gender": "men",
    "fetchDetails": true,
    "maxItems": 20,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/ssense-product-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",
    "searchTerms": ["sneakers"],
    "urls": ["https://www.ssense.com/en-us/men/sneakers"],
    "gender": "men",
    "fetchDetails": True,
    "maxItems": 20,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/ssense-product-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",
  "searchTerms": [
    "sneakers"
  ],
  "urls": [
    "https://www.ssense.com/en-us/men/sneakers"
  ],
  "gender": "men",
  "fetchDetails": true,
  "maxItems": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/ssense-product-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/ssense-product-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/yM50oDq0nepN5fTnb/builds/ikX4O6KEbgLKcpB4C/openapi.json
