Chrono24 Watches
Watch listings on Chrono24 — names, prices, descriptions and images, with optional full listing details.
1,117 rows in the dataset
Download the dataset
Get every row this scraper has collected as one CSV or JSONL file. If the data is older than maxAgeHours, the scraper is re-run with its configured settings first. The key needs the datasets:export scope.
DOWNLOAD_ID=$(curl -s -X POST "https://api.pandorlabs.com/v1/datasets/chrono24-watches/downloads" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -H "Content-Type: application/json" \ -d '{"maxAgeHours":24,"format":"csv"}' | jq -r '.id')while true; do STATUS=$(curl -s "https://api.pandorlabs.com/v1/downloads/$DOWNLOAD_ID" \ -H "Authorization: Bearer $PANDORLABS_KEY" | jq -r '.status') [ "$STATUS" != "refreshing" ] && break sleep 5donecurl -s "https://api.pandorlabs.com/v1/downloads/$DOWNLOAD_ID/file" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -o dataset.csvAt its current 1,117 rows, this download costs 12 credits when the data is fresh enough, or 24 credits when it has to be refreshed first.
The rate is 1 credit per 100 rows cached and 2 credits live, charged on the rows in the dataset when the download becomes ready. A failed refresh is not charged. The file stays downloadable for 24 hours. See pricing.
Start a run
To choose the targets yourself, send the parameters below as input. The call returns 202 with the run; the key needs the scrapers:run scope and the account a positive credit balance.
curl -X POST "https://api.pandorlabs.com/v1/scrapers/chrono24-watches/runs" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "watchType": "used", "query": "omega speedmaster", "sortOrder": "relevance", "currency": "USD" } }'A run is billed at the live rate once it finishes: 2 credits per 100 rows it collected. A run that collects nothing costs nothing.
Parameters
Every parameter is optional. Anything you leave out uses the default shown here; an unknown name or an invalid value is rejected with a 400 that lists each problem.
Targeting
watchTypeenum
Condition
Sent as `usedOrNew`. The legacy `watchTypes=U` parameter is dead — it no longer filters anything.
- Default
used- Allowed values
usednewall
querystring
Search query
Leave empty to walk the whole catalogue. A query that resolves to one brand or model makes Chrono24 redirect to a canonical listing path; pagination follows it either way.
- Default
- none
- Example
omega speedmaster
sortOrderenum
Sort order
Only nine pages of any search are reachable, so the sort order decides which 1,080 listings a run sees.
- Default
relevance- Allowed values
relevancepriceAscpriceDescnewestpopularity
priceFromnumber
Min price
In the selected currency. Combined with a max price this is the practical way to walk more of the catalogue than nine pages.
- Default
- none
- Range
- 0 to 100000000
priceTonumber
Max price
In the selected currency. Ignored if it is below the min price.
- Default
- none
- Range
- 0 to 100000000
currencyenum
Currency
Set through the `c24userprefs` cookie as well as the query string — the query parameter alone is ignored. The currency stored on a row is read back off the page, not assumed from this.
- Default
USD- Allowed values
USDEURGBPCHFJPYAUDCAD
Depth
fetchDetailsboolean
Fetch listing detail pages
Adds reference number, movement, case material and diameter, year, condition, box/papers, dealer, shipping cost and the full-size image set. Costs one request per listing.
- Default
false
maxDetailsnumber
Max detail pages per run
Listings past this keep their search-card fields; the run warns when the ceiling bites.
- Default
100- Range
- 1 to 5000
pageSizeenum
Listings per page
Only 30, 60 and 120 are honoured. Depth is capped in pages, so the largest size is the only way to reach more listings.
- Default
120- Allowed values
3060120
maxPagesnumber
Max pages
Chrono24 serves page 1-9 and answers a Cloudflare challenge for every page beyond, however many its own paginator advertises.
- Default
9- Range
- 1 to 9
maxItemsnumber
Max listings per run
Hard ceiling, applied across pages. One search cannot reach more than 1,080 listings anyway.
- Default
1080- Range
- 1 to 50000
Advanced
requestDelayMsnumber
Delay between requests (ms)
Cloudflare fronts Chrono24 and answers bursts with a challenge page. Applies between result pages and between detail pages alike.
- Default
1500- Range
- 0 to 60000
Input as JSON Schema
The same schema the API validates against. Use it as a tool input schema or to generate a typed client.
{ "type": "object", "additionalProperties": false, "properties": { "watchType": { "type": "string", "enum": [ "used", "new", "all" ], "title": "Condition", "description": "Sent as `usedOrNew`. The legacy `watchTypes=U` parameter is dead — it no longer filters anything.", "default": "used" }, "query": { "type": "string", "maxLength": 2000, "title": "Search query", "description": "Leave empty to walk the whole catalogue. A query that resolves to one brand or model makes Chrono24 redirect to a canonical listing path; pagination follows it either way.", "examples": [ "omega speedmaster" ] }, "sortOrder": { "type": "string", "enum": [ "relevance", "priceAsc", "priceDesc", "newest", "popularity" ], "title": "Sort order", "description": "Only nine pages of any search are reachable, so the sort order decides which 1,080 listings a run sees.", "default": "relevance" }, "priceFrom": { "type": "number", "minimum": 0, "maximum": 100000000, "title": "Min price", "description": "In the selected currency. Combined with a max price this is the practical way to walk more of the catalogue than nine pages." }, "priceTo": { "type": "number", "minimum": 0, "maximum": 100000000, "title": "Max price", "description": "In the selected currency. Ignored if it is below the min price." }, "currency": { "type": "string", "enum": [ "USD", "EUR", "GBP", "CHF", "JPY", "AUD", "CAD" ], "title": "Currency", "description": "Set through the `c24userprefs` cookie as well as the query string — the query parameter alone is ignored. The currency stored on a row is read back off the page, not assumed from this.", "default": "USD" }, "fetchDetails": { "type": "boolean", "title": "Fetch listing detail pages", "description": "Adds reference number, movement, case material and diameter, year, condition, box/papers, dealer, shipping cost and the full-size image set. Costs one request per listing.", "default": false }, "maxDetails": { "type": "number", "minimum": 1, "maximum": 5000, "title": "Max detail pages per run", "description": "Listings past this keep their search-card fields; the run warns when the ceiling bites.", "default": 100 }, "pageSize": { "type": "string", "enum": [ "30", "60", "120" ], "title": "Listings per page", "description": "Only 30, 60 and 120 are honoured. Depth is capped in pages, so the largest size is the only way to reach more listings.", "default": "120" }, "maxPages": { "type": "number", "minimum": 1, "maximum": 9, "title": "Max pages", "description": "Chrono24 serves page 1-9 and answers a Cloudflare challenge for every page beyond, however many its own paginator advertises.", "default": 9 }, "maxItems": { "type": "number", "minimum": 1, "maximum": 50000, "title": "Max listings per run", "description": "Hard ceiling, applied across pages. One search cannot reach more than 1,080 listings anyway.", "default": 1080 }, "requestDelayMs": { "type": "number", "minimum": 0, "maximum": 60000, "title": "Delay between requests (ms)", "description": "Cloudflare fronts Chrono24 and answers bursts with a challenge page. Applies between result pages and between detail pages alike.", "default": 1500 } }}Run, poll, read rows
The run is asynchronous. Poll it until it reaches a terminal status and its billing is paid, then page through its rows. Until then the rows endpoint answers 409 (still queued or running) or 402 (payment required).
RUN_ID=$(curl -s -X POST "https://api.pandorlabs.com/v1/scrapers/chrono24-watches/runs" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "watchType": "used", "query": "omega speedmaster", "sortOrder": "relevance", "currency": "USD" } }' | jq -r '.id')while true; do RUN=$(curl -s "https://api.pandorlabs.com/v1/runs/$RUN_ID" \ -H "Authorization: Bearer $PANDORLABS_KEY") STATUS=$(echo "$RUN" | jq -r '.status') BILLING=$(echo "$RUN" | jq -r '.billing.status') case "$STATUS" in queued|running) ;; *) [ "$BILLING" != "pending" ] && break ;; esac sleep 5doneecho "$RUN" | jq '{status, rowCount, billing}'curl -s "https://api.pandorlabs.com/v1/runs/$RUN_ID/rows?page=1&limit=100" \ -H "Authorization: Bearer $PANDORLABS_KEY"See the API reference for pagination, cancelling and listing runs.
Output fields
Rows have no fixed schema; these are the fields observed in the rows collected so far. Coverage is the share of rows where the field is present.
| Field | Type | Coverage |
|---|---|---|
| brand | string | 100% |
| category | string | 100% |
| colors | array | 100% |
| createdAt | date | 100% |
| currency | string | 100% |
| description | string | 100% |
| images | array | 100% |
| marketplace | string | 100% |
| materials | array | 100% |
| name | string | 100% |
| price | number | 100% |
| subCategory | string | 100% |
| subSubCategory | string | 10% |
| updatedAt | date | 100% |
| created_at | date | 100% |
| updated_at | date | 100% |
| _id | string | 100% |
