# Catawiki Auction Lots & Bid Monitor (`superslowsloth/catawiki-lots`) Actor

Search Catawiki auctions by keyword and get one flat row per lot: title, current bid in EUR, USD and GBP, closing time, reserve met, closed flag, watchers, buy-it-now price, photo and link. Sort by newest or closing soonest. For new-lot and bid alerts on watches, art, cars, coins and collectibles.

- **URL**: https://apify.com/superslowsloth/catawiki-lots.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.55 / 1,000 lot 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

## Catawiki Auction Lots & Bid Monitor

Search [Catawiki](https://www.catawiki.com), Europe's online auction house for
watches, art, cars, coins, jewellery and collectibles, for one or more terms and
get one flat row per lot: title, link, photo, the **current bid in euro, US
dollar and pound**, the closing time, whether the reserve is met, whether the
lot has closed, how many people are watching it and the buy-it-now price when
there is one. Run it on a schedule, sort by **newest first**, and diff the
dataset on `lot_id` to get a **new-lot alert**; sort by **closing soon** and
compare `current_bid_*` between runs to get a **bid alert**.

No account, no API key, no login, no browser. One request returns 24 lots and a
second request returns the live bids for those 24.

### Input

| Field | Notes |
|---|---|
| `searchTerms` | One or more search terms, one per line. Required. A lot found by two terms is returned once, under the first. |
| `maxItems` | Lots to read per search term (default 100, up to 2,376). 24 per page. |
| `sort` | `relevance` (Catawiki's default), `newest` (most recently listed first), `closing-soon` (lots that close first). |
| `proxyConfiguration` | Apify proxy by default. |

#### About the sorts

The sort values are the three Catawiki's own sort menu sends. Catawiki answers a
sort value it does not know with HTTP 200 and the default order, and the page
does not say which sort it applied. So:

- **closing-soon** is checked by the actor: if fewer than 80% of the first
  page's closing times come back in ascending order, the term fails (at no
  charge) rather than return rows in the wrong order. It is a share and not a
  strict order because a bid in a lot's last minutes pushes its closing time out
  between the search request and the bidding request.
- **newest** cannot be checked from the data, because the search carries no
  listing time. It was measured to return a different order from relevance, with
  the most recently opened auctions at the top, and the live test suite holds
  that in place. Treat "newest" as Catawiki's "recently added" menu entry.

### Output

Prices are numbers in the currency named by the column. Times are ISO 8601 in UTC
(`2026-10-15T19:06:20Z`).

| Field | Notes |
|---|---|
| `query` | The search term that first found this lot. |
| `lot_id`, `url`, `auction_id` | Catawiki's lot id (the number after `/l/`), the `https://www.catawiki.com/en/l/<id>-<slug>` link, and the auction (a themed batch of lots that closes together) the lot sits in. `lot_id` is stable for the life of the lot: diff on it. |
| `title`, `subtitle`, `image_url` | As Catawiki shows them. `subtitle` is null when the lot has none. |
| `current_bid_eur`, `current_bid_usd`, `current_bid_gbp` | The highest bid so far, with Catawiki's own conversion. **Null when Catawiki sent no amount**, never 0. |
| `bidding_start_time`, `bidding_end_time` | When bidding opened and when it closes. The close time moves: a bid in the last minutes extends it. |
| `closed` | True once bidding has ended. Catawiki's search still listed a lot five minutes after it closed (1 of 148 distinct lots sampled; how long it keeps closed lots is not measured), so filter on this if you only want live lots. |
| `reserve_price_set` | Whether the seller set a minimum price. |
| `reserve_price_met` | Whether the bid has reached it. **Null for a lot with no reserve**: there is nothing to meet. |
| `favorite_count` | How many people have the lot on their watch list. |
| `buy_now_price_eur` | The buy-it-now price in euro when the seller offers one, else null. |
| `buy_now_available` | Catawiki's own flag from the bidding endpoint. It does not always agree with `buy_now_price_eur` (one of three sampled lots with a price had the flag false), so use the price. |

#### Honest nulls

A field Catawiki did not send is `null`, never `0` or `""`. In the sample every
lot had a bid, so the case "nobody has bid yet" was not observed; the actor
treats a missing or non-positive amount as null rather than as a bid of 0. If
Catawiki's bidding endpoint does not return a lot at all (it leaves out an id it
does not know), the lot is still returned with its search fields and null bid,
closing, reserve, watcher and closed columns, and the log says how many.

### 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.00055** per lot row written (`listing-scraped`), i.e. $0.55 per 1,000
  lots. The bidding request that supplies the bids is included.

### How it fetches

Two plain HTTP requests per page of 24 lots, no browser:

1. `https://www.catawiki.com/en/s?q=<term>&page=<n>` - a Next.js page. The lots
   are read from the JSON in its `__NEXT_DATA__` script tag (`searchLots.lots`).
   That data carries what the lot is and **no bid amounts at all**.
2. `https://www.catawiki.com/buyer/api/v3/bidding/lots?ids=a,b,c` - Catawiki's
   own bidding API, called once per page with that page's 24 ids, joined to the
   search lots by id.

Measured 2026-10-12: HTTP 200 from an Apify datacenter exit for both, no key or
cookie, held for 30 minutes. Catawiki refuses a plain development machine (403),
so the Apify proxy is what the actor runs through, and it retries a 403, 429 or
5xx from a fresh address. Nothing was measured through a residential exit.

Things worth knowing, all measured:

- **Only 99 pages are real.** For a search of 11,663 lots, pages 1 to 99 were
  distinct and pages 100 and beyond all returned one repeated set of 24 lots.
  The actor stops at page 99 (2,376 lots) and says so in the log. To read a
  larger search, split it into narrower terms, or run it newest-first on a
  schedule. A search past its end is the other behaviour: an empty page.
- **No match is not "no results".** For a nonsense term Catawiki returns a full
  page of loosely related lots and flags it `extended_search_result`. Those lots
  are not matches, so the actor returns none of them and charges nothing; the log
  says the term had no results. A real term can also hit this: "rolex daytona
  6263" did.
- **Counts move.** Lots are listed and closed between requests, so a walk can
  show a lot twice across a page boundary; it is returned once.
- **The bidding endpoint answers in id order**, not in the order asked, which is
  why the join is by id. 24, 66 and 90 ids in one call all worked.
- **Not offered.** `per_page` and `p` are ignored by the source, so there are
  no inputs for them. Filters (category, price, closing date) are not offered.

# Actor input Schema

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

What you would type into Catawiki's search box, one term per line (for example rolex submariner, leica m3 or omega speedmaster professional). Each term is searched and read separately. A lot that two terms both find is returned once, under the first. A term Catawiki finds nothing for returns no rows: Catawiki then pads its page with loosely related lots, and those are not returned.

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

Read at most this many lots for each search term. Catawiki serves 24 per page and only its first 99 pages are real (2,376 lots); a search that is larger than that can only be read in part, so narrow it with a more specific term or read it newest-first on a schedule.

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

The order Catawiki lists the results in. 'Relevance' is Catawiki's own default. 'Newest' puts the most recently listed lots first, which is the order to use for new-lot alerts. 'Closing soon' puts the lots that close first at the top, which is the order to use for last-minute bid alerts; the actor checks that the first page really comes back in that order.

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

Apify proxy (automatic datacenter exits) is the default and was measured to work. A refused request (403, 429) is retried from a fresh address, which is what the proxy is for.

## Actor input object example

```json
{
  "searchTerms": [
    "omega speedmaster professional"
  ],
  "maxItems": 100,
  "sort": "relevance",
  "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 = {
    "searchTerms": [
        "omega speedmaster professional"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/catawiki-lots").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 = {
    "searchTerms": ["omega speedmaster professional"],
    "proxyConfiguration": { "useApifyProxy": True },
}

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

```

## MCP server setup

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

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/a4hKQ8mwpAjnBfC2w/builds/9izmgZces7xQdQVzT/openapi.json
