# Auctionet Scraper - Lots, Hammer Prices & House Contacts (`scrapersdelight/auctionet-lot-scraper`) Actor

From $3.00 per 1,000 lots, no start fee. Every Auctionet lot as one row: title, condition, category path, estimate, the full bid ladder and the hammer price across 3.9M sold lots - plus the selling house's phone, email, website and address. 39,000 live lots, 86 houses in SE, DK, FI, DE, ES and GB.

- **URL**: https://apify.com/scrapersdelight/auctionet-lot-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** E-commerce, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 per lot delivereds

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

## Auctionet Scraper — lots, hammer prices and auction house contacts

Every lot on [auctionet.com](https://auctionet.com) as one flat row: what it is, what it was
expected to make, every bid it took, what it actually sold for — **and the phone number, email,
website, street address and coordinates of the auction house selling it**.

Measured on the live site on 2026-09-18 from this machine:

| | |
|---|---|
| Live lots open for bidding | **39,189** |
| Sold lots in the hammer-price archive | **3,938,923**, back to 2014 |
| Auction houses in the public directory | **86** across Sweden, Denmark, Finland, Germany, Spain and the UK |
| Houses with live lots right now | 84 |
| Houses with archive lots | 91 |
| Currencies | SEK, EUR, GBP, DKK — amounts are **never** converted |
| Price | **$3.00 per 1,000 lots**, no run-start fee |

### What one row is

One row = one lot. Two corpora, chosen with the **Live lots or the sold archive** input:

- **Live** — the 39,189 lots currently taking bids. Changes by the minute.
  For dealers sourcing stock, and for anyone watching a category for an opportunity.
- **Sold** — the 3,938,923-lot archive of lots that have already hammered,
  each with its realized price. Static. For price databases, appraisers, insurers and anyone
  building comps.

### Field fill, counted — not claimed

Counted by the validator that ships in this repo, on **two real captured pages of 200 lots each**
(`fixtures/items_live_page1.json` and `fixtures/items_sold_company9_page1.json`, served by
auctionet.com on 2026-09-18). `""`, `"-"`, `"N/A"` and `<p></p>` are **not** counted as filled.
A real `0` or `false` is.

| Field | Live (n=200) | Sold (n=200) | What it is |
|---|---|---|---|
| `lotId` | 200/200 100.0% | 200/200 100.0% | Auctionet's lot id. Stable, and the dedupe key. |
| `title` | 200/200 100.0% | 200/200 100.0% | Lot title, in the auction house's own language. |
| `longTitle` | 200/200 100.0% | 200/200 100.0% | The site's own `"<id>. <title>"` form. |
| `lotUrl` | 200/200 100.0% | 200/200 100.0% | Full link to the lot page. |
| `catalogNumber` | 0/200 0.0% | 0/200 0.0% | The house's printed catalogue number, when it publishes one. |
| `auctionId` | 200/200 100.0% | 200/200 100.0% | Id of the auction the lot belongs to. |
| `eventId` | 0/200 0.0% | 0/200 0.0% | Id of the themed event/catalogue sale, when the lot is in one. |
| `lotType` | 200/200 100.0% | 200/200 100.0% | Sale type as the API reports it (`online`). |
| `lotStatus` | 200/200 100.0% | 200/200 100.0% | `live`, `sold` or `ended`. |
| `state` | 200/200 100.0% | 200/200 100.0% | The source's own state string (`published` / `sold`). |
| `isEnded` | 200/200 100.0% | 200/200 100.0% | Whether bidding has closed. |
| `isHammerAuctionLot` | 200/200 100.0% | 200/200 100.0% | Auctionet's `hammered` flag. It marks an in-room hammer auction lot, **not** "this lot sold" — see Honest limits. |
| `description` | 200/200 100.0% | 200/200 100.0% | Lot description as served: HTML, native language, untranslated. |
| `descriptionText` | 200/200 100.0% | 200/200 100.0% | The same description with tags stripped. |
| `condition` | 200/200 100.0% | 200/200 100.0% | Condition report as served: HTML, native language. |
| `conditionText` | 200/200 100.0% | 200/200 100.0% | The same condition report with tags stripped. |
| `placement` | 200/200 100.0% | 154/200 77.0% | Where the lot physically sits in the saleroom (shelf/rack code). |
| `categoryId` | 200/200 100.0% | 200/200 100.0% | Auctionet category id. |
| `categoryName` | 200/200 100.0% | 200/200 100.0% | Category leaf name. |
| `categoryPath` | 200/200 100.0% | 200/200 100.0% | Full category path, e.g. `Furniture / Armchairs & Chairs`. |
| `currency` | 200/200 100.0% | 200/200 100.0% | Currency of every amount on this row — SEK, EUR, GBP or DKK. Never converted. |
| `estimate` | 200/200 100.0% | 200/200 100.0% | The house's estimate (the low estimate where a range exists). |
| `upperEstimate` | 0/200 0.0% | 0/200 0.0% | Upper estimate, on the rare lots that publish a range. |
| `startingBid` | 200/200 100.0% | 200/200 100.0% | Opening bid. |
| `nextBidAmount` | 200/200 100.0% | 200/200 100.0% | What the next valid bid would have to be. |
| `suggestedBidAmounts` | 200/200 100.0% | 200/200 100.0% | The three increments the site offers a bidder. |
| `currentBid` | 121/200 60.5% | 200/200 100.0% | Highest bid so far, or `null` if nobody has bid. |
| `hammerPrice` | 0/200 0.0% | 200/200 100.0% | Realized price — **sold lots only**, `null` on a live lot. |
| `bidCount` | 200/200 100.0% | 200/200 100.0% | Number of bids. A real `0`, not a missing value. |
| `bids` | 121/200 60.5% | 200/200 100.0% | The full bid ladder: amount, anonymised bidder number, auto-bid flag, timestamp. |
| `reserveMet` | 200/200 100.0% | 200/200 100.0% | Whether the reserve has been met. |
| `reserveAmount` | 77/200 38.5% | 200/200 100.0% | The reserve, where the house discloses it. |
| `prefersStartingBidToEstimate` | 200/200 100.0% | 200/200 100.0% | The house shows the starting bid rather than an estimate. |
| `estimateRatio` | 121/200 60.5% | 200/200 100.0% | Hammer (or current bid) ÷ estimate. `null` when either is missing — never a 0. |
| `endsAt` | 200/200 100.0% | 200/200 100.0% | Close time, ISO 8601 UTC. |
| `endsAtUnix` | 200/200 100.0% | 200/200 100.0% | Close time as a unix timestamp. |
| `publishedAt` | 200/200 100.0% | 200/200 100.0% | When the lot went up, ISO 8601 UTC. |
| `publishedAtUnix` | 200/200 100.0% | 200/200 100.0% | Publication time as a unix timestamp. |
| `daysListed` | 200/200 100.0% | 200/200 100.0% | Days between publication and close. |
| `secondsUntilClose` | 200/200 100.0% | 0/200 0.0% | Seconds left to bid. `null` once the lot has ended. |
| `houseName` | 200/200 100.0% | 200/200 100.0% | Selling auction house. |
| `houseCity` | 200/200 100.0% | 200/200 100.0% | City, from the lot when present and from the house record otherwise. |
| `houseCompanyId` | 200/200 100.0% | 200/200 100.0% | Auctionet company id — the exact join key to the house record. |
| `houseSlug` | 200/200 100.0% | 200/200 100.0% | The house's slug on auctionet.com. |
| `houseUrl` | 200/200 100.0% | 200/200 100.0% | The house's page on auctionet.com. |
| `housePhone` | 200/200 100.0% | 200/200 100.0% | **House phone.** Neither rival ships this. |
| `houseEmail` | 200/200 100.0% | 200/200 100.0% | **House email.** Neither rival ships this. |
| `houseWebsite` | 200/200 100.0% | 200/200 100.0% | The house's own website. |
| `houseStreetAddress` | 200/200 100.0% | 200/200 100.0% | Full street address. |
| `housePostalCode` | 182/200 91.0% | 200/200 100.0% | Postcode, parsed out of the address block. |
| `houseCountry` | 200/200 100.0% | 200/200 100.0% | Country. |
| `houseLatitude` | 200/200 100.0% | 200/200 100.0% | Latitude, from the house page's own map link. |
| `houseLongitude` | 200/200 100.0% | 200/200 100.0% | Longitude, from the house page's own map link. |
| `imageUrl` | 200/200 100.0% | 200/200 100.0% | Primary photo, full size. |
| `imageUrlMedium` | 200/200 100.0% | 200/200 100.0% | Primary photo, 640px. |
| `imageUrlThumb` | 200/200 100.0% | 200/200 100.0% | Primary photo, thumbnail. |
| `imageUrls` | 200/200 100.0% | 200/200 100.0% | Every photo, full size. |
| `imageCount` | 200/200 100.0% | 200/200 100.0% | How many photos the lot has. |
| `images` | 200/200 100.0% | 200/200 100.0% | Every photo in all four sizes Auctionet publishes. |
| `sourceUrl` | 200/200 100.0% | 200/200 100.0% | The exact API URL this row came off. |
| `scrapedAt` | 200/200 100.0% | 200/200 100.0% | When this run read the lot. |

`catalogNumber`, `eventId` and `upperEstimate` are 0/200 on both pages above, and they are kept
anyway — because they are not random gaps, they are **catalogued-sale fields**. Most Auctionet lots
are rolling online sales with a single estimate and no catalogue. The houses that run catalogued
sales fill all three on every lot: on `fixtures/items_sold_company528_catalogued.json`, a real
captured page from Ma San Auction, it is **200/200 for all three**, with `upperEstimate` always at
or above `estimate`. Across the wider 8,000-row archive sample it is
3.0% / 3.0% /
3.0%. So: use them when you scrape a catalogue house, and
**never advertise an estimate RANGE off this source in general** — 97% of lots publish one number.

#### The auction house contact block

Measured over **all 86 houses** in the directory, every page fetched today
(86/86 HTTP 200):

| | filled | |
|---|---|---|
| `housePhone` | 86/86 | 100.0% |
| `houseEmail` | 86/86 | 100.0% |
| `houseWebsite` | 86/86 | 100.0% |
| `houseStreetAddress` | 86/86 | 100.0% |
| `houseCountry` | 86/86 | 100.0% |
| `houseLatitude` / `houseLongitude` | 86/86 | 100.0% |
| `housePostalCode` | 76/86 | 88.4% |
| phone **and** email on the same house | 86/86 | 100.0% |

The join is on the integer `company_id` that every lot already carries, not on a name match.
**100.0% of live lots**
reach a contact. On the archive it is 99.5%: 5 houses have
left Auctionet and their directory pages are gone, taking 17,284 archive lots with them
(Leiflers Auktionshus, id 47; Dreweatts Online, id 185; Art and Design Auctions, id 284; Auktionshaus von Brühl, id 75; Auktionshaus Dannenberg, id 52). Those lots still arrive — with
`housePhone: null` — and the run summary names the ids.

### A real row

Not an example. This is lot `5239651` exactly as this Actor emitted it on 2026-09-18,
trimmed only by cutting repeated bids and images:

```json
{
  "lotId": 5239651,
  "title": "FÅTÖLJ, 1970-tal.",
  "longTitle": "5239651. FÅTÖLJ, 1970-tal.",
  "lotUrl": "https://auctionet.com/en/5239651-armchair-1970s",
  "catalogNumber": null,
  "auctionId": 9332440,
  "eventId": null,
  "lotType": "online",
  "lotStatus": "sold",
  "state": "sold",
  "isEnded": true,
  "isHammerAuctionLot": false,
  "description": "<p>Höjd ca 88 cm\n<br />Sitthöjd ca 40 cm\n<br />Armstöd till armstöd ca 69 cm.\n<br />Skinn, snurrbar fot krom.</p>",
  "descriptionText": "Höjd ca 88 cm\n\nSitthöjd ca 40 cm\n\nArmstöd till armstöd ca 69 cm.\n\nSkinn, snurrbar fot krom.",
  "condition": "<p>Fläckar, repor, mindre skador.</p>",
  "conditionText": "Fläckar, repor, mindre skador.",
  "placement": "H1C",
  "categoryId": 18,
  "categoryName": "Armchairs & Chairs",
  "categoryPath": "Furniture / Armchairs & Chairs",
  "currency": "SEK",
  "estimate": 1200,
  "upperEstimate": null,
  "startingBid": 300,
  "nextBidAmount": 850,
  "suggestedBidAmounts": [
    850,
    900,
    950
  ],
  "currentBid": 800,
  "hammerPrice": 800,
  "bidCount": 4,
  "bids": [
    {
      "bidId": 42739950,
      "bidderNumber": 3,
      "amount": 800,
      "isAutoBid": false,
      "reserveMet": true,
      "placedAt": "2026-09-18T19:07:16.000Z"
    },
    {
      "bidId": 42739881,
      "bidderNumber": 3,
      "amount": 400,
      "isAutoBid": false,
      "reserveMet": false,
      "placedAt": "2026-09-18T19:05:37.000Z"
    },
    … 2 more bids
  ],
  "reserveMet": true,
  "reserveAmount": 800,
  "prefersStartingBidToEstimate": false,
  "estimateRatio": 0.6667,
  "endsAt": "2026-09-18T19:09:00.000Z",
  "endsAtUnix": 1789758540,
  "publishedAt": "2026-09-11T12:58:40.000Z",
  "publishedAtUnix": 1789131520,
  "daysListed": 7.26,
  "secondsUntilClose": null,
  "houseName": "Gomér & Andersson Linköping",
  "houseCity": "Linköping",
  "houseCompanyId": 9,
  "houseSlug": "ga",
  "houseUrl": "https://auctionet.com/en/ga",
  "housePhone": "+46 (0)13-375210",
  "houseEmail": "linkoping@gomer-andersson.se",
  "houseWebsite": "http://www.gomer-andersson.se/",
  "houseStreetAddress": "Industrigatan 36, 582 77 Linköping, Sweden",
  "housePostalCode": "582 77",
  "houseCountry": "Sweden",
  "houseLatitude": 58.419569,
  "houseLongitude": 15.606969,
  "imageUrl": "https://images.auctionet.com/thumbs/hd_item_5239651_fba399e43a.jpg",
  "imageUrlMedium": "https://images.auctionet.com/thumbs/w640_item_5239651_fba399e43a.jpg",
  "imageUrlThumb": "https://images.auctionet.com/thumbs/thumb_item_5239651_fba399e43a.jpg",
  "imageUrls": [
    "https://images.auctionet.com/thumbs/hd_item_5239651_fba399e43a.jpg",
    "https://images.auctionet.com/thumbs/hd_item_5239651_30c573550b.jpg",
    … 2 more
  ],
  "imageCount": 7,
  "images": [
    {
      "thumb": "https://images.auctionet.com/thumbs/thumb_item_5239651_fba399e43a.jpg",
      "mini": "https://images.auctionet.com/thumbs/mini_item_5239651_fba399e43a.jpg",
      "medium": "https://images.auctionet.com/thumbs/w640_item_5239651_fba399e43a.jpg",
      "full": "https://images.auctionet.com/thumbs/hd_item_5239651_fba399e43a.jpg"
    },
    … 3 more
  ],
  "sourceUrl": "https://auctionet.com/api/v2/items.json?per_page=200&page=1&company_id=9&is=ended",
  "scrapedAt": "2026-09-18T19:09:59.670Z"
}
```

### Inputs

| Input | What it does |
|---|---|
| **Live lots or the sold archive** | `live` (default) or `sold`. |
| **Search term** | Free text across titles and descriptions. Descriptions are in each house's own language, so a Swedish word finds more Swedish lots than its English translation. |
| **Auction houses** | Pick from all 86, each shown with its live and sold lot counts. |
| **Extra auction house IDs** | Numeric `company_id`, for the 5 departed houses that still have archive lots. |
| **Categories** | Auctionet's own tree, all 160 nodes with counts. Picking a top-level category includes everything under it. |
| **Countries** | SE, DK, FI, DE, ES, GB. |
| **Minimum / maximum estimate** | In the lot's own currency. |
| **Only lots that already have a bid** | 121/200 on the captured ending-soonest page — but 3,797/8,000 (47.5%) across the wider live sample, which is the honest corpus-wide number. |
| **Sort order** | Ending soonest, most recent, highest/lowest estimate, highest/lowest bid, most/fewest bids. |
| **Join the house's contact details** | On by default. |
| **Include the full bid ladder** | On by default. Sold lots average 7.05 bids, live lots 3.32. |
| **Include every image URL** | On by default. Every lot in the sample has a photo — 5.44 on average live, 6.70 sold. |
| **Max lots** | Hard cap on rows delivered **and charged**. Default 1,000. |
| **Proxy** | Apify Proxy, datacenter. Verified below. |

Every value you can type is checked against the live site **before a single row is delivered**, and
a bad one fails the run with the valid list rather than charging you. That is not defensive
politeness — this API answers HTTP 200 to a filter it does not recognise and returns the
**unfiltered catalogue**: `category_id=999999` returns all 39,189
live lots (captured in `fixtures/ignored_param_category_999999.json`), an unknown `company_id` does
the same, and `country_code=UK` returns 0 rows that look exactly like a real empty result. One typo
would otherwise be a bill for the whole store.

### How it gets past the source's 10,000-lot ceiling

This is the one thing about auctionet.com that is not in any documentation, and the reason a naive
scraper cannot give you the archive.

**Every distinct query serves at most 10,000 items.** At `per_page=200` (the ceiling; asking for
500 or 1,000 silently returns 200), page 50 returns 200 lots, **page 51 returns an 88-byte body with
zero lots**, and page 60 returns `HTTP 302` back to page 1. `pagination.total_pages` caps itself at
`10,000 / per_page`, so a 247,488-lot bucket reports 50 pages, not 1,238. A page-walker sized off
`total_entries` therefore stops dead at 10,000 rows and reports success. Both boundary responses are
in `fixtures/window_perpage20_page500.json` and `…page501.json`.

So this Actor **partitions** until every query fits inside the window, preferring the axis measured
lossless:

1. **Category** — the 25 top-level categories sum to 39,189 of
   39,189 live lots and 3,938,923 of
   3,938,923 sold lots — **exactly 100.00% of both** — and a parent query includes
   its children, so the tree can be descended until each node fits.
2. **Auction house** — read free off the search page's own facet panel, which returns every
   `company_id` *with its exact lot count for that query*, so a house split costs one request and no
   probing. 84 houses cover 100% of live lots; 91 cover
   89.1% of the archive.
3. **Country** — the same facet panel, six values.
4. **Sort order** — not a partition but a window multiplier: a different sort opens a different
   10,000-lot window over the same bucket. Measured pairwise on a >10k bucket: `end_asc` and
   `end_desc` are **not** opposites (104/200 ids in common on page 1), but
   `estimate_asc`/`estimate_desc`/`bid_asc`/`bid_desc`/`bids_count_asc`/`bids_count_desc` are
   disjoint (0/200 overlap), so seven orderings reach up to 70,000 lots in one bucket.

Rows are deduplicated by lot id, so an overlap costs requests and never a duplicate row. Whatever no
axis can reach is **counted and reported** in `RUN_SUMMARY.coverage`, not quietly dropped.

Two more things the partitioner does that save you money:

- It only splits when the window is actually in the way. Asking for 40 lots out of a 3.9M-lot
  archive needs no partitioning at all, so the demo run costs ~11 API calls rather than 25 category
  probes.
- An estimate filter cannot be pushed to the source (`min_estimate`, `estimate_from` and friends
  are all silently ignored), so instead of scanning and discarding, the Actor pins the estimate sort
  and walks only the matching end. Measured on one house's archive with
  `minEstimate: 5000, maxItems: 60`: **200 lots read instead of 5,357, one page instead of 27,
  1.2 MB instead of 13.2 MB** — same 60 rows delivered.

### Billing

**$3.00 per 1,000 lots delivered. No run-start fee.** One charge event, `lot-scraped`, billed per
row actually written to your dataset.

- Rows removed by your filters are never delivered and never charged.
- **Max lots is checked against rows produced, not batches flushed** — a cap of 37 delivers and
  charges exactly 37. The validator proves this at caps of 1, 37, 199, 200, 201 and 350.
- A run that matches nothing costs nothing. So does a run that fails validation.
- The house contact join, the bid ladder, the hammer price and the category path are all included
  in the same rate — there is no enrichment upsell.

### Honest limits

1. **The archive contact join tops out at 99.5%.** 5 auction houses have left
   Auctionet; their lots stay in the archive but their directory pages are gone, so
   17,284 sold lots arrive with `housePhone: null`. The run summary lists the ids.
2. **Sold lots only in the archive.** `is=ended` returns *sold* lots — 200/200 in the captured page
   are `state: "sold"` with a bid on every one. Auctionet does not publish unsold/passed lots, so
   **sell-through rate is not derivable from this source.** Anyone who tells you otherwise is
   inferring it.
3. **There is no `hammer_price` field.** `hammerPrice` is `max(bids[].amount)` on a sold lot. It is
   cross-checked against the lot page's own embedded record in the validator: for lot 5239651 the
   API says 800 SEK against an estimate of 1200, and the page — which renders in
   the *viewer's* currency, not the house's — shows the same ratio to within 0.02%.
4. **`hammered` does not mean "sold".** Auctionet's own flag marks an in-room hammer auction lot;
   it was `false` on 200/200 lots of a page of *sold* archive rows. It ships as
   `isHammerAuctionLot` so its name cannot mislead you. Sold-ness comes from `state`.
5. **Amounts are never converted.** Every figure is in the lot's `currency` (SEK 347, EUR 28, GBP 18, DKK 7 across the 400 sample rows).
   Comparing a SEK hammer price to a GBP one is your conversion to do.
6. **Descriptions and conditions are HTML in the house's own language** — Swedish, Danish, German or
   Spanish. They are passed through verbatim plus a plain-text twin. Nothing is translated, because a
   guessed translation yields a plausible wrong answer rather than an error.
7. **`upperEstimate`, `catalogNumber` and `eventId` are catalogue-sale fields, not general ones**
   (3.0%, 3.0% and
   3.0% of 8,000 archive rows overall — but 200/200 at a house
   that runs catalogued sales). Filter to those houses if you need them; do not build a range-based
   model on the corpus as a whole.
8. **A capped run is a prefix, not a sample.** Buckets are walked depth-first, so
   `maxItems: 500` returns whole categories and houses rather than 500 lots spread evenly across the
   corpus. Use the filters when you want a specific slice.
9. **Live lots drift while you read them.** `total_entries` moved from 39,548 to 39,047 over three
   hours of measurement today as lots closed. A 4% cross-page id overlap under a shifting sort is
   normal and is deduplicated by id.
10. **`bidderNumber` is a paddle number, not a person.** Auctionet publishes an anonymous per-lot
    bidder index (1, 2, 3…) on the public lot page. No account, name or identity is available, and
    none is collected.

### Source and legal

- API: `https://auctionet.com/api/v2/items.json` — public, unauthenticated, CORS-open
  (`access-control-allow-origin: *`), and the live site's own data source: the lot page's
  `data-react-props` carries the same record.
- `robots.txt`, captured verbatim in `fixtures/robots.txt`:
  ```
  User-Agent: *
  Disallow: /admin/
  Disallow: /*/my$
  Disallow: /*/my/*
  Sitemap: https://auctionet-exports.s3-eu-west-1.amazonaws.com/sitemap.xml.gz
  ```
  Neither `/api/` nor the lot pages nor the auction-house pages are disallowed.
- Only public listing and business-contact data is read. No login, no personal data, no bidder
  identities.
- Auctionet is a trademark of its owner. This is an independent tool and is not affiliated with,
  endorsed by or connected to Auctionet.

### Transport, measured

| | |
|---|---|
| Cloudflare challenge | none — the API returns HTTP 200 to a request with **no User-Agent header at all** |
| Apify Proxy, datacenter | HTTP 200; a 842,986-byte body arrived complete, Content-Length matched |
| Apify Proxy, RESIDENTIAL | HTTP 200, same |
| Sustained datacenter load | 12 large bodies through fresh sessions: 11 complete, 0 truncated, 1 socket hang-up (retried on a fresh session) |
| Throughput | 60 requests at concurrency 10 → 60/60 HTTP 200, 9,792 lots, 12.6 s |
| 1,000 lots delivered | 89 requests, 3.4 MB, 13 s (live) / 90 requests, 4.9 MB, 13 s (sold) |
| Peak memory on a 3,000-lot run | 225 MB, which is why this Actor is configured at 512 MB |

Large bodies are checked against `Content-Length` before parsing, because a multi-hundred-KB body
can truncate mid-stream on a datacenter proxy **while still reporting HTTP 200**. A non-200, or a
200 carrying a challenge page, is treated as a transport failure and retried on a fresh proxy
session — never as "this query is empty". If a large fraction of requests fail, the run fails
rather than reporting a partial read as a complete one.

### What you get that the alternatives do not

Two other Actors scrape this site. Both were read in full on 2026-09-18. Both charge Apify's
`apify-actor-start` event as well as a per-result rate, and that start fee is billed once per
gigabyte of allocated memory — 2 GB and $0.01 for one, 4 GB and $0.02 for the other.

| | this Actor | `stealth_mode/auctionet-product-search-scraper` | `crawlerbros/auctionet-scraper` |
|---|---|---|---|
| Price (FREE tier) | **$0.003/lot, no start fee** | $0.01/result + a start fee | $0.005/result + a start fee |
| A 200-lot run costs | **$0.60** | $2.01 | $1.02 |
| Fields per row | **61** | 19 | 16 |
| Lot description + condition | ✅ raw **and** plain text | ❌ | ✅ (stripped only) |
| Category path | ✅ from the live 160-node tree | ❌ | ✅ |
| Estimate / starting bid / next bid | ✅ | estimate only | estimate only |
| Current bid / bid count | ✅ | display string only | ✅ |
| **Full bid ladder with timestamps** | ✅ | ❌ | ❌ |
| **Realized hammer price** | ✅ 100% of archive rows | ❌ | via a keyword-only "ended" mode |
| **Hammer ÷ estimate ratio** | ✅ | ❌ | ❌ |
| **Auction house phone + email** | ✅ 100.0% | ❌ | ❌ |
| **House address + coordinates** | ✅ 100.0% | ❌ | ❌ |
| Reserve amount / reserve met | ✅ | reserve met only | reserve met only |
| Images | ✅ all 4 sizes | ✅ 1 size | ✅ 3 sizes |
| Filter by house / category / country | ✅ validated against the live site | ✅ (unvalidated) | category only |
| Gets past the 10,000-per-query ceiling | ✅ partitioned, with coverage reported | ❌ | ❌ (README caps a run at 5,000) |
| Run summary reconciling source total vs delivered | ✅ | ❌ | ❌ |
| Per-field fill measured and published | ✅ | ❌ | ❌ |

Fields the rivals ship that this Actor **cuts**, and why:

- `highlighted`, `canBeFollowed`, `isFollowing`, `showAuctionEndTime` — viewer/session UI state, not
  lot data. `highlighted` was `false` on 48/48 cards in a real captured search page.
- `amountLabel`, `amountValue`, `amountTitle` — display strings converted into the *viewer's* session
  currency ("201 EUR"). This Actor ships the numbers in the lot's own currency instead, plus the
  `currency` code, so you can convert with a rate you control.
- `shortTitle` — the truncated card title. `title` is the full one, and `longTitle` is the site's
  `"<id>. <title>"` form.
- `eventTitle` — the themed-sale name. It is only on the search card, not the API, and it was
  populated on 1/48 cards; `eventId` ships instead.
- `scrapedAt` (crawlerbros) is **not** cut — it ships, along with `sourceUrl`, the exact API URL each
  row came off.

### Run summary

Every run writes `RUN_SUMMARY` to the key-value store: lots delivered (which is exactly lots
charged), lots read, rows dropped by your filters, duplicate ids skipped, how the query was
partitioned and anything no axis could reach, **per-field fill counted on your own rows**,
house-contact coverage with the company ids that no longer have a directory page, currency and
house breakdowns, and every URL that failed after retries. On a failed run it carries the reason and
the failing URLs, so an unreachable source is never mistaken for an empty one.

### Validating this build

```
node offline_validate.mjs
```

No network, no platform, no token. It parses the captured bytes in `fixtures/` with the same modules
the Actor runs, and asserts every number in this README.

# Actor input Schema

## `lotStatus` (type: `string`):

Which lots to return. `all` is every lot Auctionet exposes; `open` only lots still accepting bids; `ended` only lots whose hammer has fallen, which is the set that carries a realised hammer price. Applied server-side where the API supports it, so excluded lots are never fetched or charged.

## `searchTerm` (type: `string`):

Free-text search across lot titles and descriptions, exactly as the site's own search box treats it. Measured: "clock" matches 1,242 live lots. Descriptions are in each house's own language (Swedish, Danish, German, Spanish), so a Swedish word finds more Swedish lots than its English translation does.

## `auctionHouses` (type: `array`):

All 86 auction houses in Auctionet's public directory, with the live and sold lot counts measured on 2026-09-18. Leave empty for every house. Picking houses here is also the cheapest way to scope a run: each house is a separate query, so a 5-house run reads only those 5 houses.

## `companyIds` (type: `array`):

Numeric Auctionet company_id values, for houses that are not in the dropdown above. Five houses have left the platform but still have archive lots and no longer have a directory page, so they can only be reached this way — and they are the only lots in this Actor that come back with no house phone or email: 47 = Leiflers Auktionshus (10,553 sold lots), 185 = Dreweatts Online (3,000 sold lots), 284 = Art and Design Auctions (1,920 sold lots), 75 = Auktionshaus von Brühl (1,045 sold lots), 52 = Auktionshaus Dannenberg (766 sold lots). Anything not recognised by the live site fails the run before it charges you.

## `categories` (type: `array`):

Auctionet's own category tree, all 160 nodes, with live and sold counts measured on 2026-09-18. Picking a top-level category includes everything underneath it — verified: the 25 top-level categories sum to 39,189 of 39,189 live lots and 3,938,923 of 3,938,923 sold lots, i.e. exactly 100% of both. Leave empty for everything.

## `countryCodes` (type: `array`):

The country the auction house sits in. Leave empty for all six. Note the code for the UK is GB, not UK — the site returns zero rows for "UK" rather than an error, which is exactly the kind of silent empty result this Actor refuses to hand you, so "UK" is rejected up front.

## `minEstimate` (type: `integer`):

Keep only lots whose estimate is at least this much, in the LOT's own currency (SEK, EUR, GBP or DKK — the API never converts, and neither does this Actor). Auctionet's API has no estimate parameter (measured: min_estimate and estimate_from are silently ignored), so this is applied after reading, which means a filtered run reads more lots than it delivers. You are only charged for what is delivered.

## `maxEstimate` (type: `integer`):

Upper bound on the estimate, in the lot's own currency. Same caveat as the minimum: applied after reading, charged only on delivery.

## `onlyWithBids` (type: `boolean`):

Measured on 8,000 live lots from 40 houses: 3,797 (47.5%) already have at least one bid and 52.5% have none yet. Every sold lot has bids by definition, so this only changes a live run.

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

Pinning a sort order also gives up this Actor's way of reaching past the source's 10,000-lot-per-query pagination ceiling, which works by reading the same bucket under several different orders. On an uncapped run over a bucket bigger than 10,000 lots that costs coverage, and the run summary will say by how much.

## `includeHouseContact` (type: `boolean`):

Adds housePhone, houseEmail, houseWebsite, houseStreetAddress, housePostalCode, houseCountry, houseLatitude, houseLongitude, houseSlug and houseUrl, read from the house's own page on auctionet.com and joined on the exact integer company_id that every lot carries. Measured over all 86 directory houses: phone 86/86, email 86/86, website 86/86, street address 86/86, coordinates 86/86, postcode 76/86. Costs one extra request per distinct house in your results, fetched once and reused.

## `includeBidHistory` (type: `boolean`):

Adds `bids`: every bid on the lot with its amount, anonymised bidder number, auto-bid flag and timestamp. `currentBid`, `hammerPrice` and `bidCount` are always present whether this is on or off. Measured on 200 sold lots: 7.05 bids each (median 5); on 200 live lots, 3.32 (median 1).

## `includeAllImages` (type: `boolean`):

Adds `images` (all four sizes Auctionet publishes for each photo) and `imageUrls`. `imageUrl`, `imageUrlMedium`, `imageUrlThumb` and `imageCount` are always present. Measured on 400 lots (200 live, 200 sold): every single one has at least one photo — 5.44 on average live, 6.70 sold.

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

Hard cap on lots delivered, and therefore on lots charged — the cap is checked against rows PRODUCED, so you are never billed a part-batch past it. 0 means no cap. For scale: the whole live corpus is 39,189 lots (about 4 minutes) and the whole sold archive is 3,938,923. A capped run walks buckets in order, so it returns whole categories and houses rather than a random sample — use the filters above when you want a specific slice.

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

Verified through Apify Proxy on 2026-09-18: datacenter returned HTTP 200 on every call including a complete 842,986-byte body, and so did RESIDENTIAL. auctionet.com sits behind Cloudflare but serves this API to a request with no User-Agent at all, so there is no fingerprint gate — datacenter is plenty. Switch to RESIDENTIAL only if you ever see repeated transport failures in the log.

## Actor input object example

```json
{
  "lotStatus": "live",
  "auctionHouses": [],
  "companyIds": [],
  "categories": [],
  "countryCodes": [],
  "onlyWithBids": false,
  "sortBy": "default",
  "includeHouseContact": true,
  "includeBidHistory": true,
  "includeAllImages": true,
  "maxItems": 40,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per Auctionet lot: title, description, condition, category path, estimate, starting and next bid, the full bid ladder, the realized hammer price on sold lots, close time, images, and the selling auction house's phone, email, website, street address and coordinates.

## `runSummary` (type: `string`):

RUN_SUMMARY: what auctionet.com said was there against what was actually read and delivered — lots delivered (== lots charged), lots read, rows dropped by your filters, duplicate lot ids skipped, how the 10,000-per-query pagination window was partitioned around and anything it could not reach, per-field fill counted on this run's own rows, house-contact coverage, and every URL that failed after retries.

# 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 = {
    "lotStatus": "live",
    "includeHouseContact": true,
    "includeBidHistory": true,
    "includeAllImages": true,
    "maxItems": 40
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/auctionet-lot-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 = {
    "lotStatus": "live",
    "includeHouseContact": True,
    "includeBidHistory": True,
    "includeAllImages": True,
    "maxItems": 40,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/auctionet-lot-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 '{
  "lotStatus": "live",
  "includeHouseContact": true,
  "includeBidHistory": true,
  "includeAllImages": true,
  "maxItems": 40
}' |
apify call scrapersdelight/auctionet-lot-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/auctionet-lot-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/HMdAf6gkNIn4NWIQV/builds/Z3sNrsbBlabdTFDAt/openapi.json
