# ASOS Sale Watch: Products & Markdowns by Country (`superslowsloth/asos-products`) Actor

ASOS products from 11 country stores as flat rows: name, brand, price, price before the markdown, discount percent, lowest price in the last 30 days, colour, photos, URL. Search any terms; filter to sale items, a minimum discount, newest or price order.

- **URL**: https://apify.com/superslowsloth/asos-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 product scrapeds

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

## ASOS Sale Watch: Products & Markdowns by Country

Read [ASOS](https://www.asos.com) search results as flat rows: product name,
brand, current price, the **price before the markdown**, the discount percent,
the **lowest price of the last 30 days**, colour, photos and the product link.
Eleven country stores, each in its own currency. Tick **Only products on sale**
or pick a **minimum discount** and ASOS itself returns only marked-down
products. Schedule it daily and diff the runs: that is an ASOS sale watch.

No account, no API key, no login, no browser. One request returns up to 200
products.

### Input

| Field | Notes |
|---|---|
| `store` | `us` (USD), `com` United Kingdom (GBP), `au` (AUD), `de`, `fr`, `es`, `it`, `nl` (EUR), `se` (SEK), `dk` (DKK), `pl` (PLN). Default `us`. |
| `queries` | Search terms, one per line (`jeans`, `white trainers`). Required. A product found by two terms is returned once, under the first. |
| `onlyOnSale` | Keep only products ASOS currently marks down (its own `range=sale` filter). |
| `minDiscount` | `20` to `60`: keep only products marked down by at least that percent, using ASOS's own Discount % bands. Anything but `0` (no minimum) implies `onlyOnSale`. |
| `sort` | `relevance` (ASOS's default ranking), `newest`, `price_low`, `price_high`. |
| `maxItems` | Products to read per search term (default 100, up to 1,000). |
| `proxyConfiguration` | Apify proxy by default. |

A search needs at least one term: ASOS answers a search without one with an
error, so there is no "list everything" mode and the actor refuses such a run
before it charges anything.

### Output

| Field | Notes |
|---|---|
| `query`, `store` | The term that first found the product, and the store (`us`, `com`, ...). |
| `product_id`, `colourway_id`, `product_code` | ASOS's product id (the number in `/prd/210492282`), the id of the colour the search listed, and its internal product code. The product id is the row's identity. |
| `name`, `brand`, `colour` | As ASOS prints them, in the store's language. |
| `url` | Absolute product page on the store, `https://www.asos.com/<store>/<brand>/<slug>/prd/<id>` (the UK store has no store segment). |
| `product_type` | `Product`, `MixMatchProduct` (a co-ord piece) or `MixMatchGroup`, as sent. |
| `currency`, `price`, `price_text` | The current price as a number, with its currency, and exactly as the storefront prints it (`$34.99`, `25,99 €`). |
| `previous_price` | The price before the markdown. **Null on every product that is not marked down.** |
| `on_sale` | ASOS's own `isMarkedDown` flag. |
| `discount_percent` | `(previous_price - price) / previous_price`, one decimal, **only when both exist and the price is below the previous one**. |
| `lowest_price_30d` | The lowest price of the last 30 days as ASOS reports it. Null when ASOS reports none (see below). |
| `is_outlet_price`, `has_multiple_prices` | ASOS's `isOutletPrice` and `hasMultiplePrices` flags. |
| `image_url`, `additional_image_urls`, `video_url` | Photos and the product video, with `https://` added (ASOS sends them without a scheme). |
| `is_new`, `is_selling_fast` | ASOS's "New" and "Selling fast" badges. |

#### Honest nulls

A field ASOS did not send is `null`, never `0` or `""`:

- `previous_price` and `discount_percent` are null for a product that is not
  marked down. Measured 2026-10-12 over 6,383 product records (many of them
  the same page fetched twice) from the US, UK, AU, IT, NL, SE, DK, PL, DE, FR
  and ES stores: ASOS sent a previous price on every
  marked-down product and on no other, so a null means "no markdown", not
  "free".
- `lowest_price_30d` is null on every product that is not marked down and on a
  few marked-down ones (the Netherlands store sent none for some). It can sit
  *above* the current price: a UK jeans listing at 19.99 GBP, down from 35.99,
  reported a 30-day low of 23.99.
- `currency`: ASOS puts the currency code on the price block of most responses,
  but on some it is `null` (measured: the Italy store at 72 rows a page, with
  prices printed without a symbol). The row then carries the currency of the
  store the request asked for, which is the one ASOS prices that store in.
- `colour`, `brand`, `video_url`, `price`: null when ASOS sends no value.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.0007** per product row written (`product-scraped`), i.e. $0.70 per 1,000
  products.

### How it fetches

It calls the JSON search API behind ASOS's own storefront
(`www.asos.com/api/product/search/v2/`), up to 200 rows per request, with no
cookie, token or login, presenting a Chrome TLS fingerprint through the Apify
datacenter proxy. Measured 2026-10-12 from Apify datacenter addresses with that
fingerprint: HTTP 200 JSON on all eleven stores listed above. **ASOS's edge
(Akamai) answers roughly two requests in five with an `Access Denied` page (HTTP
403\) whatever the request is.** The actor treats that as a refusal, rotates to a
fresh address and retries each page up to eight times; a page that is still
refused is reported in the run log and skipped. Expect run logs to show retries.

Things worth knowing, all measured:

- **Only the first 1,000 results of any search can be read.** `itemCount` for
  `jeans` on the US store was 5,919 and the page at offset 1,000 is empty; a
  page that straddles the line is cut short (200 rows asked at offset 950 gave
  50\). The run log says when a search found more than it can serve. To reach
  beyond it, narrow the search: another term, `onlyOnSale`, a `minDiscount`.
- **The sale filter is ASOS's.** `onlyOnSale` sends `range=sale`; for `jeans` on
  the US store that was 1,298 of 5,920 products, the count of the response's own
  Sale facet. `minDiscount` sends ASOS's Discount % bands (`20-30%`, `30-40%` ...
  `60% or more`) as a list; the counts add up to the facet counts (bands 3 to 6
  gave 622 against 187+184+164+83 on the same search).
- **Counts move.** `itemCount` for the same search drifted by a handful between
  requests minutes apart (5,914 to 5,919), and the sale rack changes through the
  day, so a walk can show a product twice across a page boundary; it is returned
  once.
- **Some searches report exactly 100 hits.** Rare terms (`lime green jeans`)
  came back as `itemCount` 100 with real rows to the end; the actor stops at the
  count ASOS reports.
- **The stores are fixed combinations.** ASOS refuses a wrong
  store/language/currency pairing with a 400, so the input offers the pairings
  that answered. Measured and *not* offered: Canada, Switzerland, Ireland,
  Norway, Rest of World, Singapore, Malaysia, Hong Kong (HTTP 400, "No valid
  store was found"), Russia (answered one product), and the Rest-of-Europe
  store, which answered but whose product pages redirect to the UK site root.
- **Product links resolve.** A real id answers 200 on the store's page and an
  unknown one 404 (measured on the US store); every store's pages were checked
  with a product of that store.
- **Sort.** `newest` sends `freshness`; `price_low` and `price_high` send
  `priceasc` / `pricedesc`. The single products come back in price order; a
  `MixMatchGroup` (a set priced as a whole) is placed out of order (a $377.00
  group followed five single products running $295.20 down to $237.91). ASOS's
  own `recommended` / `relevance` names are rejected by the API (HTTP 400), so
  the default ranking is "no sort parameter".

# Actor input Schema

## `store` (type: `string`):

Which ASOS country store to search. Each store has its own currency, language and prices: United States (USD), United Kingdom (GBP), Australia (AUD), Germany, France, Spain, Italy and Netherlands (EUR), Sweden (SEK), Denmark (DKK) and Poland (PLN). Only stores that returned products when measured on 2026-10-12 are listed.

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

What you would type into the ASOS search box, one term per line (for example jeans, white trainers or Levi's 501). Each term is searched and read separately; a product that two terms both find is returned once, under the first. ASOS's own search matches the term in the chosen store (on the Italy store both jeans and the Italian profumo found products, measured 2026-10-12).

## `onlyOnSale` (type: `boolean`):

Keep only products ASOS currently marks down (its own Sale filter). Each row then carries the price before the markdown and the discount percent. With this off, sale and full-price products come back mixed, in ASOS's own ranking.

## `minDiscount` (type: `string`):

Keep only products marked down by at least this much, using ASOS's own Discount % bands (20-30%, 30-40%, ... 60% or more). Choosing anything but 'No minimum' also turns on 'Only products on sale'. ASOS applies the filter itself, so Max products counts matching products only.

## `sort` (type: `string`):

Order of the results. 'ASOS ranking' is the order the ASOS search page shows by default. Newest first, cheapest first and most expensive first are ASOS's own sort options. With a Max products limit, the order decides which products you get.

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

Read at most this many products for each search term. ASOS serves up to 200 per request but only the first 1,000 results of any one search, however many it reports finding (5,919 for jeans in the US store), so 1,000 is the limit. To reach beyond it, narrow the search: another term, the sale filter or a minimum discount.

## `proxyConfiguration` (type: `object`):

Apify proxy (automatic datacenter exits) is the default. ASOS's edge refuses about two requests in five from the datacenter pool with an Access Denied page; the actor retries each refused page from a fresh address, which is what the proxy is for.

## Actor input object example

```json
{
  "store": "us",
  "queries": [
    "jeans"
  ],
  "onlyOnSale": false,
  "minDiscount": "0",
  "sort": "relevance",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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 = {
    "queries": [
        "jeans"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/asos-products").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 = {
    "queries": ["jeans"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/asos-products").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 '{
  "queries": [
    "jeans"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/asos-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/asos-products"
        }
    }
}
```

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/jstXmF6VUq7ub5b4P/builds/H8C2jIG3LkoSgNMaB/openapi.json
