# Chrono24 Scraper - Luxury Watches, Prices, Specs & Reviews (`abotapi/chrono24-scraper`) Actor

Scrape Chrono24 luxury watch listings by keyword, filters or URL. Extract 50+ fields including brand, model, reference number, price, year, condition, movement, case specifications, seller details, location, images and more.

- **URL**: https://apify.com/abotapi/chrono24-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 4 total users, 0 monthly users, 96.6% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 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

## Chrono24 Scraper: Watch Listings, Specs, Seller & Review Data

Chrono24 Scraper turns Chrono24, the global marketplace for new and pre-owned watches, into structured JSON. Search by keyword and filters, or paste any mix of detail, brand, model, and search URLs. Each listing comes back with brand, model, reference number, price, year, condition, the full movement and case spec sheet, seller and location, images, and the seller's rating and recent buyer reviews. Export to JSON, CSV, or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **50+ fields per watch on the full record:** the complete spec sheet (movement, caliber, case, dial, bracelet, clasp) plus seller, certification, listing code, and reference details.
- **Seller rating and recent buyer reviews** (date, stars, watch, reviewer, text) attached to every fully-enriched listing at no extra fetch.
- **Two modes:** keyword + filter search, or direct URLs, with detail, brand, model, and search links mixed freely in one run.
- **Card mode for cheap, fast harvesting** of search and list pages, or full-detail mode for the complete spec sheet, chosen per run.
- **Rich filters:** price, year, condition, case material, dial color, bracelet material, seller type, country, reference number, and more, with six sort orders.
- **Chrono24's own curated Top Deals collection** with one toggle, complete with the site's own per-listing savings percentage in card mode.
- **Automatic pagination, de-duplication, and per-record output**, with optional flattening to `specs_*` columns for spreadsheet export.

### Use Cases

- **Dealers and resellers:** track competitor pricing and stock across brands and reference numbers to price your own inventory.
- **Collectors:** monitor a specific reference or model for new listings, price drops, or newly reappeared stock.
- **Market analysts:** build historical pricing datasets by brand, condition, and case material for valuation models.
- **Deal hunters:** pull Chrono24's curated Top Deals collection sorted by savings to spot the steepest discounts first.
- **Marketplace researchers:** compare seller ratings, review samples, and seller type (dealer vs. private) across a search.

### Data You Get

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

| Field | Example |
| --- | --- |
| `listingId` | `"00000001"` |
| `title` | `"Rolex Submariner Date"` |
| `brand` | `"Rolex"` |
| `model` | `"Submariner Date"` |
| `referenceNumber` | `"126610LN"` |
| `price` | `12500` |
| `currency` | `"USD"` |
| `availability` | `"InStock"` |
| `condition` | `"Used (Very good)"` |
| `year` | `"2022"` |
| `location` | `"Germany, Bavaria, Munich"` |
| `sellerCountry` | `"DE"` |
| `sellerUsername` | `"sample_dealer"` |
| `certificationStatus` | `"Basic"` |
| `isDeal` | `false` |
| `specs_movement` | `"Automatic"` |
| `specs_caseMaterial` | `"Steel"` |
| `specs_caseDiameter` | `"41 mm"` |
| `specs_dialColor` | `"Black"` |
| `specs_functions` | `"Date"` |
| `sellerRating` | `4.8` |
| `reviewCount` | `12` |
| `images` | `["https://img.chrono24.com/images/uhren/00000001-Square480.jpg"]` |
| `listingUrl` | `"https://www.chrono24.com/rolex/submariner--id00000001.htm"` |

A full-detail record also carries `description`, `priceDisplay`, `availabilityText`, `conditionText`, `scopeOfDelivery`, `gender`, Chrono24's internal `productType`/`marketingType`/`collectionId`/`modelId`/`modelVariantId`/`refId`, the remaining spec columns (`specs_baseCaliber`, `specs_powerReserve`, `specs_jewels`, `specs_waterResistance`, `specs_bezelMaterial`, `specs_crystal`, `specs_dialNumerals`, `specs_braceletMaterial`, `specs_braceletColor`, `specs_clasp`, `specs_claspMaterial`, `specs_productSafety`), a `reviews` array, a sample of the seller's recent reviews shown on the listing page (not the seller's full review history; date, rating, watch, reviewer name and country, received/shipped-from, text), and `reviewCount`, the number of reviews in that sample, `thumbnail`, `rowType`, and `scrapedAt`. `buyingAgent` (name, office, region) appears only on the minority of listings handled through Chrono24's own buying-agent/concierge program. `rawHtmlBytes` is a diagnostic byte count, only added when Include raw size marker is on. Chrono24 does not publish a structured was-price, original-price, or MSRP field on any listing, curated collections included, so this scraper does not fabricate one; the one real savings figure it does publish, a rendered "-N%" badge on the curated Top Deals collection, is picked up as `discountPercent` on lightweight cards (see below), while the full detail page carries only the boolean `isDeal`. Lightweight cards from a search, brand, model, or search-URL results page (fetch full details off) carry a narrower shape instead: `rowType`, `listingId`, `watchId`, `title`, `price`, `currency`, `discountPercent` (Top Deals collection only, `null` otherwise), `sellerType`, `countryCode`, `thumbnail`, `images`, `mainImage`, `imageUrls`, `badge`, `listingUrl`, `sourceUrl`, and `scrapedAt`, without any of the spec, seller-review, or Chrono24-internal fields above. A pasted single-listing URL (`--id...htm`) never returns this card shape: it always returns the full-detail record described above, regardless of the Fetch full details toggle (see "How to Use"). Incremental mode adds `changeType`, `changedFields`, `firstSeenAt`, and `lastSeenAt` to every record; these are absent when incremental mode is off.

### How to Use

1. Pick a **mode**: `search` (keyword + filters) or `url` (paste any mix of detail, brand, model, and search links).
2. In search mode, set a keyword and filters, or turn on **Top Deals collection only** to browse Chrono24's own curated deals instead. In URL mode, paste your links.
3. Switch **Fetch full details** on for the full spec sheet, or off for fast, cheap cards from results pages. Set **Max listings** (and optionally **Max pages**) to control run size and cost.
4. Click **Start**, then download the dataset as JSON, CSV, or Excel, or read it through the API.

**Search by keyword with filters (full detail):**

```json
{
  "mode": "search",
  "searchQuery": "Rolex Submariner",
  "searchParams": { "priceFrom": 8000, "priceTo": 15000, "usedOrNew": "used", "caseMaterials": "steel" },
  "sortBy": "price-asc",
  "fetchDetails": true,
  "maxListings": 50
}
```

**Fast card harvesting (no per-listing fetch):**

```json
{
  "mode": "search",
  "searchQuery": "Omega Speedmaster",
  "fetchDetails": false,
  "maxListings": 200
}
```

**Chrono24's curated Top Deals collection, sorted by the site's own savings percentage (card mode, needed to see `discountPercent`):**

```json
{
  "mode": "search",
  "dealsOnly": true,
  "sortBy": "savings-desc",
  "fetchDetails": false,
  "maxListings": 50
}
```

**Direct URLs, detail + model + search mixed, with Fetch full details off:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.chrono24.com/rolex/submariner--mod4.htm",
    "https://www.chrono24.com/patek-philippe/nautilus--id00000001.htm",
    "https://www.chrono24.com/search/index.htm?query=Daytona&dosearch=true"
  ],
  "fetchDetails": false,
  "maxListings": 0
}
```

This last example returns two different row shapes in one run: the model and search URLs return lightweight cards (Fetch full details is off), but the pasted detail URL still returns a full-detail record and is still billed the detail-enrichment charge, within whatever Max listings budget remains after the model and search URLs are walked. A single-listing URL has no lightweight card equivalent on Chrono24, so whenever it's read it is always fetched and enriched in full; Fetch full details only decides the row shape for brand, model, and search URLs (and for search mode). Set **Max listings** to `0` (unlimited) as shown here, or make sure the other URLs' budget doesn't consume the whole cap, or the pasted detail URL may never be reached in card mode, since list URLs are walked first.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/chrono24-scraper").call(run_input={"mode": "search", "searchQuery": "Rolex Submariner", "maxListings": 20})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["price"], listing["currency"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/chrono24-scraper').call({ mode: 'search', searchQuery: 'Rolex Submariner', maxListings: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

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

#### How Max listings and Max pages cap a run

**Max listings** (`maxListings`) is the single cap on total output; set it to `0` for the hard maximum of 100000 listings. **Max pages** (`maxPages`) is a safety bound on how many result pages (120 listings each) the actor walks per search, brand, model, or search URL; set it to `0` for unlimited too. Either way, a walk also stops on its own the moment a page adds no new listings, so a run never loops on a search that has run out of fresh results.

#### Resume and recurring updates

Two distinct features:

- **Resume from a previous run** (`resumeFromRunId`) continues one specific interrupted crawl: paste a previous run ID or dataset ID and this run skips every listing that run already collected, so a large pull finishes without paying for or re-returning what you already have. A run ID or dataset ID that cannot be read stops the run before anything is scraped.
- **Incremental mode** (`incrementalMode`) is for scheduling this actor against the same search or URL set again and again (daily, for example) and getting back only what changed. State is kept automatically per search/URL and detail-mode setup, or name it yourself with `stateKey` to control sharing across runs. Every listing then carries `changeType`: `NEW` on first sight, `UPDATED` (with `changedFields`) when a tracked field genuinely changes (every scraped field is compared except `scrapedAt`, the seller-review list, `reviewCount`, `sellerRating`, their copies inside `seller` (`seller.rating`, `seller.reviewCount`), `trackingId` and anything you list in `ignoreFieldsForChanges`; ignored fields are still returned), or `UNCHANGED` when nothing did (suppressed and not billed unless `emitUnchanged` is on). `EXPIRED` and `REAPPEARED` are opt-in and gated: `EXPIRED` only fires with `emitExpired` on, and only after a run that scanned the *entire* tracked search without being capped by Max listings or Max pages, resumed, or missing a page it could not read. `REAPPEARED` can then only happen afterward, for a listing an earlier `emitExpired` run had marked gone; without ever turning `emitExpired` on, a schedule will only ever see `NEW`, `UPDATED`, and (if `emitUnchanged` is on) `UNCHANGED`. Combine `incrementalMode` with `emitExpired` and set `maxListings`/`maxPages` to `0` if you need a complete scan every run.

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

Optionally pipe results into the apps you already use through Model Context Protocol (MCP) connectors. Authorize a connector once under Apify, Settings, Integrations, then select it in the input. Notion receives a page per item; other connectors get a best-effort write or digest. The connector receives a condensed, human-readable summary per item (a title plus key fields), not the full JSON; the complete record always stays in the Apify dataset. Leave the field empty to skip. Supported: Notion, Linear, Airtable, Apify.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword + filters) or `url` (paste links). |
| `searchQuery` | string | no default (prefill: `Rolex Submariner`) | Keyword for search mode. Empty browses everything matching the filters. |
| `searchParams` | object | no default (prefill: `{}`) | Filters: `priceFrom`, `priceTo` (USD), `year`, `usedOrNew`, `caseMaterials`, `dialColor`, `braceletMaterial`, `sellerType`, `countryIds`, `referenceNumber`, `caseDiameter`, `man`, `certified`, `inStock`. Friendly words or raw Chrono24 ids. |
| `manufacturerIds` | string | no default | Typed filter: Brand ids, comma-separated; each id is applied as its own filter. Overrides the same key in `searchParams`. |
| `catalogCollectionId` | string | no default | Typed filter: Catalog collection id. Overrides the same key in `searchParams`. |
| `catalogModelId` | string | no default | Typed filter: Catalog model id. Overrides the same key in `searchParams`. |
| `watchTypes` | string | no default | Typed filter: `N` for new, `U` for used. Overrides the same key in `searchParams`. |
| `condition` | string | no default | Typed filter: Condition code. Overrides the same key in `searchParams`. |
| `specials` | array | no default | Typed filter: Delivery contents. Overrides the same key in `searchParams`. |
| `movementTypes` | string | no default | Typed filter: Movement type. Overrides the same key in `searchParams`. |
| `gender` | string | no default | Typed filter: Gender. Overrides the same key in `searchParams`. |
| `waterproof` | string | no default | Typed filter: Water resistance code. Overrides the same key in `searchParams`. |
| `functions` | array | no default | Typed filter: Functions. Overrides the same key in `searchParams`. |
| `styles` | array | no default | Typed filter: Styles. Overrides the same key in `searchParams`. |
| `braceletColor` | array | no default | Typed filter: Bracelet colors. Overrides the same key in `searchParams`. |
| `stockInfo` | string | no default | Typed filter: Stock. Overrides the same key in `searchParams`. |
| `trackingId` | string | no default | Tag copied onto every output row. |
| `priceFrom` | integer | no default | Typed filter: Min price. Overrides the same key in `searchParams`. |
| `priceTo` | integer | no default | Typed filter: Max price. Overrides the same key in `searchParams`. |
| `year` | string | no default | Typed filter: Year. Overrides the same key in `searchParams`. |
| `certified` | boolean | no default | Typed filter: Certified only. Overrides the same key in `searchParams`. |
| `caseDiameter` | string | no default | Typed filter: Case diameter (mm). Overrides the same key in `searchParams`. |
| `caseMaterials` | array | no default | Typed filter: Case materials. Overrides the same key in `searchParams`. |
| `dialColor` | array | no default | Typed filter: Dial colors. Overrides the same key in `searchParams`. |
| `braceletMaterial` | array | no default | Typed filter: Bracelet materials. Overrides the same key in `searchParams`. |
| `sellerType` | string | no default | Typed filter: Seller type. Overrides the same key in `searchParams`. |
| `countryIds` | string | no default | Typed filter: Seller countries, comma-separated ISO-2 codes such as `DE,NL`; each code is applied as its own filter. Overrides the same key in `searchParams`. |
| `sortBy` | string | `relevance` | `relevance`, `price-asc`, `price-desc`, `newest`, `popularity`, `savings-desc` (Top Deals collection only; falls back to relevance elsewhere). |
| `dealsOnly` | boolean | `false` | Browse Chrono24's curated Top Deals collection instead of a regular search. Ignores `searchQuery` and `searchParams` (not supported by that page); `sortBy` still applies, including `savings-desc`. |
| `urls` | array | a model page, the Top Deals page and a brand page | Detail (`--id`), brand (`/rolex/index.htm`), model (`--mod`), search (`/search/index.htm?dosearch=true&query=...`) or Top Deals URLs (URL mode). In URL mode, leaving `urls` out scrapes the three default examples instead of finding nothing. URLs of any other shape are skipped with a warning in the run log, and a URL that returns no listings is logged as such. |
| `fetchDetails` | boolean | `true` | On: full spec sheet per listing. Off: lightweight cards from results pages for search/brand/model/search URLs; pasted detail URLs are always fully fetched regardless. |
| `maxReviewsPerListing` | integer | `20` | Caps the seller reviews attached to each listing (1 to 1000). Reviews are read from the same page, so no extra billing applies. |
| `fetchSellerReviews` | boolean | `true` | Attach seller rating + recent buyer reviews to each fully-enriched listing. Read in the same pass as the details, so it adds no extra fetch. Only applies when Fetch full details is on (or on a pasted detail URL, which is always fully fetched). |
| `flatten` | boolean | `true` | Also expose nested specs as flat `specs_*` columns. |
| `includeRaw` | boolean | `false` | Attach a `rawHtmlBytes` diagnostic field. |
| `maxListings` | integer | `20` | The single cap on output (maximum 100000). `0` means the maximum, 100000. |
| `maxPages` | integer | `0` | Safety bound on pages per search URL (120 listings per page). `0` means unlimited: the run stops at Max listings, or when a page stops returning new listings. |
| `resumeFromRunId` | string | no default | Continue one specific interrupted run: paste its run ID or dataset ID and this run skips listings already collected there. |
| `incrementalMode` | boolean | `false` | Recurring/scheduled monitoring of the same search or URL set; see "Resume and recurring updates". |
| `stateKey` | string | no default | Name a monitoring campaign (incremental mode) to keep its state stable, or leave empty for an automatic key derived from the search/URL and detail settings. |
| `emitUnchanged` | boolean | `false` | Incremental mode: also return (and bill) listings unchanged since the last run, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Incremental mode: also return (and bill) listings from a previous run no longer found, marked `EXPIRED`. Only produced after a complete scan; see "Resume and recurring updates". |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode: extra field names (any depth) or dotted paths such as `specs.movement` to leave out of change detection. They are still returned in the output. |
| `proxy` | object | no default (prefill: Apify Proxy enabled) | Connection settings; see "Plan Requirement". |
| `mcpConnectors` | array | no default | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | no default | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

#### Supported URL shapes (URL mode)

| URL shape | Example | What you get |
| --- | --- | --- |
| Detail | `/rolex/submariner--id00000001.htm` | One full-detail record, always, regardless of Fetch full details (detail enrichment applies). |
| Brand | `/rolex/index.htm` | Cards, or full details when Fetch full details is on; paginated. |
| Model | `/rolex/submariner--mod4.htm` (also `--imod`) | Cards or full details; paginated. |
| Search | `/search/index.htm?query=Daytona&dosearch=true` | Cards or full details; paginated. |
| Top Deals | Chrono24's curated Top Deals page | Cards with `discountPercent`, or full details. |

If you leave `urls` out in URL mode, the three default examples (a model page, the Top Deals page and a brand page) are scraped. A search URL pasted without `dosearch=true` gets that flag added, because Chrono24 shows an empty search page without it. Any other URL shape is skipped with a warning in the run log naming the URL and the reason, and a URL that returns no listings is logged as such. If every URL is skipped, the run says no usable query or URLs were provided.

#### Typed filters and tracking id

Search mode accepts each filter as its own input (brand ids, price range, new or used, condition code, certified, delivery contents, movement, gender, case diameter, year, water resistance, functions, styles, materials, colors, seller type, stock, seller countries). They override the same key in the Filters JSON, which keeps working. Sort order "Savings" only applies with the Top Deals collection. Set Tracking id to copy a tag onto every output row. Rows also carry additive fields where the page exposes them. Cards: `mainImage`, `imageUrls` and `badge` (the Top Deals savings badge). Full-detail records: `mainImage`, `imageUrls`, `certifiedStatus`, `listingStatus`, `watchStockInfo`, `subModel`, `conditionNew` (Chrono24's own new or used split), `shippingCost` (the seller's published shipping amount), `sellerLocation` (`countryName`, `region`, `countryCode`) and `seller` (`merchantLogin`, `rating`, `reviewCount`). The price currency is the one Chrono24 serves to the connection; it is not selectable.

#### Seller review limit

`maxReviewsPerListing` (default 20, 1 to 1000) caps how many seller reviews are attached to each fully fetched listing. `reviewCount` is the number of reviews found on the listing page and `sellerRating` is the published rating; both stay the same whatever the cap is, only the attached `reviews` list is shortened. The cap does not add any charge.

#### Output views

The dataset has these views in the Console: Overview (listings and cards), Specifications, Listing details, Seller reviews (one row per review) and Incremental changes (`changeType`, `changedFields`, first and last seen).

#### Pricing events

This actor is pay per event: a charge when a run starts, a charge for each listing pushed to the dataset ("Listing result"), and a "Detail enrichment" charge for each listing fully fetched (Fetch full details on, or any pasted single-listing URL). Seller reviews and views add nothing. In incremental mode, unchanged listings are not pushed and so are not billed unless Emit unchanged is on. The Pricing tab shows the current rates.

### Output Example

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

```json
{
  "rowType": "detail",
  "listingId": "00000001",
  "watchId": "00000001",
  "listingUrl": "https://www.chrono24.com/rolex/submariner--id00000001.htm",
  "title": "Rolex Submariner Date",
  "brand": "Rolex",
  "model": "Submariner Date",
  "referenceNumber": "126610LN",
  "listingCode": "AAA000",
  "price": 12500,
  "currency": "USD",
  "availability": "InStock",
  "condition": "Used (Very good)",
  "year": "2022",
  "location": "Germany, Bavaria, Munich",
  "sellerCountry": "DE",
  "sellerUsername": "sample_dealer",
  "certificationStatus": "Basic",
  "isDeal": false,
  "scopeOfDelivery": "Original box, original papers",
  "gender": "Men's watch/Unisex",
  "specs": {
    "movement": "Automatic",
    "caliber": "3235",
    "baseCaliber": "3235",
    "powerReserve": "70 h",
    "jewels": "31",
    "caseMaterial": "Steel",
    "caseDiameter": "41 mm",
    "waterResistance": "30 ATM",
    "bezelMaterial": "Ceramic",
    "crystal": "Sapphire crystal",
    "dialColor": "Black",
    "braceletMaterial": "Steel",
    "clasp": "Fold clasp",
    "functions": "Date"
  },
  "sellerRating": 4.8,
  "reviewCount": 12,
  "reviews": [
    {
      "date": "2026-01-01",
      "rating": 5,
      "watch": "Rolex Submariner",
      "received": "2026-01-01",
      "shippedFrom": "United States of America",
      "reviewerName": "Jane D.",
      "reviewerCountry": "United States of America",
      "text": "Sample review text appears here."
    }
  ],
  "images": ["https://img.chrono24.com/images/uhren/00000001-Square480.jpg"],
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

A lightweight card (Fetch full details off, on a search, brand, model, or search-URL page) instead returns just `rowType: "card"`, `listingId`, `watchId`, `title`, `price`, `currency`, `discountPercent` (Top Deals collection only, `null` otherwise), `sellerType`, `countryCode`, `thumbnail`, `images`, `mainImage`, `imageUrls`, `badge`, `listingUrl`, `sourceUrl`, and `scrapedAt`, with none of the spec, seller, or review fields above.

### Plan Requirement

Chrono24 protects its pages with an anti-bot challenge on every connection type, datacenter and residential alike, so no proxy setting alone gets through. The actor handles this itself: it probes exits, obtains a clearance for a chosen exit and retries on a different connection when one is refused. Expect some runs to be slower during busy challenge periods. Keep the default Apify Proxy setting under **Proxy & connection**; a residential group is optional and gives the widest rotation for large or frequent runs. Apify free plans do not include residential proxies, and on those plans the actor continues on a backup connection that may be slower or return fewer results. When the requested pages cannot be loaded at all, the run fails with one of these messages instead of finishing empty:

- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Chrono24 pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."

### FAQ

#### How much does it cost?

You pay per run start, per listing returned, and an extra detail-enrichment charge for each listing that is fully fetched (Fetch full details on, or any pasted single-listing URL, which is always fully fetched regardless of that toggle). The **Pricing** tab shows the current rates. Use **Max listings** to cap the cost of any run.

#### Is it legal to scrape Chrono24?

This actor collects only publicly available marketplace listing data. You are responsible for how you use it: follow Chrono24's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listings can include a seller's username, general location, and reviews from other users; handle that data responsibly and avoid republishing it in ways that identify individuals.

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

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated listings by default, and unchanged ones are not billed. Turning on `emitExpired` also surfaces listings that disappeared, but only after a run that scans the whole tracked search; see "Resume and recurring updates" for the exact gating.

#### Does "Fetch full details" apply to a pasted single-listing URL?

No. A pasted detail URL (`--id...htm`) has no lightweight card equivalent on Chrono24, so it is always fetched and enriched in full, and always billed the detail-enrichment charge, whether Fetch full details is on or off. The toggle only changes the row shape for search results and for pasted brand, model, and search URLs.

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

If Chrono24 refuses every request this run makes, or a pasted `resumeFromRunId` cannot be resolved, the run stops with a clear failed status instead of quietly finishing with zero items, so "nothing could be read" is never confused with "nothing matches". A search or URL set that genuinely matches nothing, or an incremental run where every listing was unchanged, still finishes normally with zero new items and a status message explaining why.

#### 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 classifieds data?

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

<table>
<tr><td>🏷️ <a href="https://apify.com/abotapi/bol-com-scraper"><b>bol.com Scraper</b></a><br>Scrape bol.com products: title, price, list price and discount, EAN, brand, images...</td><td>🏷️ <a href="https://apify.com/abotapi/vinted-fr-scraper"><b>Vinted.fr</b></a><br>Scrape Vinted.fr, France's C2C second-hand fashion marketplace: search with...</td></tr>
<tr><td>🏷️ <a href="https://apify.com/abotapi/marktplaats-nl-scraper"><b>Marktplaats.nl Scraper</b></a><br>Scrape Marktplaats.nl listings by keyword or URL. Extract title, price, condition...</td><td>🏷️ <a href="https://apify.com/abotapi/thredup-scraper"><b>ThredUp Scraper</b></a><br>Scrape ThredUp resale listings by keyword, department, brand, size, condition, price or...</td></tr>
<tr><td>🏷️ <a href="https://apify.com/abotapi/gumtree-uk-scraper"><b>Gumtree.com Scraper</b></a><br>Scrape Gumtree.com classifieds across motors, property, jobs, services, pets and goods...</td><td>🏷️ <a href="https://apify.com/abotapi/daangn-scraper"><b>Daangn Scraper</b></a><br>Scrape Daangn (당근) marketplace listings by keyword and region. Returns 35+ fields per...</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/chrono24-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`):

How to find watches: build a search from a keyword + filters, or paste your own URLs.

## `searchQuery` (type: `string`):

Keyword to search, e.g. 'Submariner', 'Omega Speedmaster', 'Patek Nautilus'. Leave empty to browse everything matching the filters below.

## `searchParams` (type: `object`):

Filter the search. Accepts friendly words or raw Chrono24 ids. Keys: priceFrom, priceTo (USD), year, usedOrNew ('new' | 'used'), caseMaterials (steel | yellow gold | rose gold | white gold | platinum | titanium | ceramic), dialColor (black | blue | green | white | silver | grey | meteorite), braceletMaterial (steel | leather | rubber | yellow gold), sellerType ('chrono24 direct' | 'professional dealer' | 'private sellers'), countryIds (ISO code, e.g. DE), referenceNumber, caseDiameter (mm), man (brand slug, e.g. rolex), certified (true), inStock (true).

## `manufacturerIds` (type: `string`):

Comma-separated Chrono24 brand ids (search mode); each id is sent as its own filter.

## `catalogCollectionId` (type: `string`):

Chrono24 catalog collection id (search mode).

## `catalogModelId` (type: `string`):

Chrono24 catalog model id (search mode).

## `priceFrom` (type: `integer`):

Minimum price (USD).

## `priceTo` (type: `integer`):

Maximum price (USD).

## `watchTypes` (type: `string`):

U = used, N = new.

## `condition` (type: `string`):

Chrono24 condition code.

## `certified` (type: `boolean`):

Only certified listings.

## `specials` (type: `array`):

Codes: 102 box and papers, 103 box only, 104 papers only.

## `movementTypes` (type: `string`):

Chrono24 movement type id 1-5.

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

1401 or 1402.

## `caseDiameter` (type: `string`):

Single value or range in mm.

## `year` (type: `string`):

Single year or range.

## `waterproof` (type: `string`):

Chrono24 water resistance code (901-923, 1350, 1351).

## `functions` (type: `array`):

Complication ids.

## `styles` (type: `array`):

Watch style ids.

## `caseMaterials` (type: `array`):

Case material words or ids.

## `dialColor` (type: `array`):

Dial color words or ids.

## `braceletMaterial` (type: `array`):

Bracelet material words or ids.

## `braceletColor` (type: `array`):

Bracelet color ids.

## `sellerType` (type: `string`):

Dealer, PrivateSeller or C24Direct.

## `stockInfo` (type: `string`):

InStock, OnOrder or OnRequest.

## `countryIds` (type: `string`):

Comma-separated ISO-2 seller countries, for example DE,NL (search mode); each code is sent as its own filter.

## `trackingId` (type: `string`):

Optional tag copied onto every output row.

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

Result order (search mode). 'Savings' only has an effect when Top Deals collection only is on; elsewhere it falls back to Relevance.

## `dealsOnly` (type: `boolean`):

Browse Chrono24's own curated 'Top Deals' collection instead of a regular search. When on, Search keyword and Filters are ignored (that combination is not supported by Chrono24's Top Deals page); only Sort by still applies (including the 'Savings, highest first' order, only available here). Chrono24 does not publish a structured was-price/original-price/MSRP field on any listing, but the Top Deals collection does show a rendered '-N%' savings badge per listing, which card mode (Fetch full details off) extracts as discountPercent. That badge is not present on the full detail page; use isDeal there as the deal signal instead.

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

Any mix of Chrono24 detail (--id), brand (/rolex/index.htm), model (--mod) or search URLs. Detail URLs return one watch each; brand / model / search URLs paginate automatically.

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

On (default): fetch each listing's detail page for the full 50+ field spec sheet. Off: emit lightweight cards straight from the results page (faster, fewer fields, no per-listing fetch).

## `fetchSellerReviews` (type: `boolean`):

Attach the seller's rating and recent buyer reviews (date, stars, watch, reviewer, text) to each listing. Read from the listing page in the same pass, so it adds no extra cost. Only applies when Fetch full details is on.

## `maxReviewsPerListing` (type: `integer`):

Upper limit on seller reviews attached to each listing. Only applies when Include seller reviews is on.

## `flatten` (type: `boolean`):

Also expose nested specs as flat specs\_\* columns for easy CSV / spreadsheet export.

## `includeRaw` (type: `boolean`):

Attach a rawHtmlBytes field per record (diagnostics). Does not change other fields.

## `maxListings` (type: `integer`):

The one cap on output. Stops the run once this many listings are collected. Set 0 for the maximum of 100000.

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

Safety bound on pages walked per search / brand / model URL (120 listings per page). Set 0 for unlimited: the run then stops at Max listings, or when a page stops returning new listings, whichever comes first.

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

Works on every Apify plan including Free. Residential gives the widest IP rotation. Free plans fall back to a backup connection.

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

Paste a previous run ID or dataset ID to continue a large crawl of listings without returning or charging for listings already collected there. Use this after an interrupted run, or when continuing a listing pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead.

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

Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED listings. A listing counts as UPDATED when any scraped field changes (price, availability, condition, specs, description, images, seller details and so on); changedFields names what moved. Never compared: scrapedAt, the seller-review list, the review count and the seller rating, including the copies inside the seller block (these move on their own), plus anything you add under Ignore fields below. Turn on "Emit unchanged" or "Emit expired" only when you also want those listings returned (and billed). State is kept separately for each search/URL and detail-mode setup; use State key when you want 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/URL and detail settings -- different searches then never mix state with each other.

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

Off by default. Turn on to also return listings 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 the full snapshot every run.

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

Off by default. Turn on to also return listings 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 listings capped it or when Resume was used. This returns -- and bills -- extra synthetic rows, so leave it off unless you need expiry tracking.

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

Optional. Field names to exclude from UPDATED change detection, in addition to the built-in handling (the seller-review list, review count and seller rating never trigger UPDATED). A plain name such as images ignores that field at any depth; a dotted path such as specs.movement ignores exactly that nested field. Ignored fields are still returned in the output.

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

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. 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 item pages are created. Required to enable the Notion export; ignored by other connectors.

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

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

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "Rolex Submariner",
  "searchParams": {},
  "sortBy": "relevance",
  "dealsOnly": false,
  "urls": [
    "https://www.chrono24.com/rolex/submariner--mod4.htm",
    "https://www.chrono24.com/watches/top-deals.htm",
    "https://www.chrono24.com/rolex/index.htm"
  ],
  "fetchDetails": true,
  "fetchSellerReviews": true,
  "maxReviewsPerListing": 20,
  "flatten": true,
  "includeRaw": false,
  "maxListings": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

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

No description

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

No description

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

No description

## `specs` (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",
    "searchQuery": "Rolex Submariner",
    "searchParams": {},
    "urls": [
        "https://www.chrono24.com/rolex/submariner--mod4.htm",
        "https://www.chrono24.com/watches/top-deals.htm",
        "https://www.chrono24.com/rolex/index.htm"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/chrono24-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",
    "searchQuery": "Rolex Submariner",
    "searchParams": {},
    "urls": [
        "https://www.chrono24.com/rolex/submariner--mod4.htm",
        "https://www.chrono24.com/watches/top-deals.htm",
        "https://www.chrono24.com/rolex/index.htm",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/chrono24-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",
  "searchQuery": "Rolex Submariner",
  "searchParams": {},
  "urls": [
    "https://www.chrono24.com/rolex/submariner--mod4.htm",
    "https://www.chrono24.com/watches/top-deals.htm",
    "https://www.chrono24.com/rolex/index.htm"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/chrono24-scraper --silent --output-dataset

```

## MCP server setup

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