# BidSpotter Auction Scraper — Industrial & Machinery Lots (`scrapersdelight/bidspotter-auction-scraper`) Actor

Paste any BidSpotter URL — catalogue, category, search or lot — for one row per lot: lot title, lot number, current bid, opening price, currency, bid count, watchers, category, brand, lot location, venue address, exact closing time, auction title, auctioneer and lot URL. No login, no browser.

- **URL**: https://apify.com/scrapersdelight/bidspotter-auction-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** E-commerce, Business, Automation
- **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 returneds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## BidSpotter Auction Scraper — Industrial & Machinery Lots

Scrape [BidSpotter](https://www.bidspotter.com) — the biggest industrial and commercial online
auction marketplace in North America — into a clean lot feed. **One row per lot**, carrying
**lotTitle**, **lotNumber**, **currentBid**, **openingPrice**, **currency**, **bidCount**,
**watchers**, **category**, **brand**, **itemType**, **lotLocation**, **venueAddress**,
**closingAt**, **auctionTitle**, **auctioneer** and **lotUrl**.

Paste any BidSpotter URL you can copy out of the address bar — an auction catalogue, a category,
a keyword search, or a single lot — or pick a category / keyword in the input form.
**No login. No API key. No headless browser. No CAPTCHA solving.**

**Scope, measured on 2026-08-12:** BidSpotter's own sitemap listed **2,217 live auction
catalogues**; the `metalworking` category alone listed **12,679 live lots across 212 pages**.
Both counts were read off the live site, not quoted from a marketing page.

```json
{
  "startUrls": ["https://www.bidspotter.com/en-us/for-sale/metalworking"],
  "maxLots": 50,
  "includeLotDetails": true
}
```

Click **Try for free** and hit **Start** — that is the input this Actor ships prefilled. Three
verification runs of exactly that input each returned **50 lots, 0 duplicates and 0 lot-detail
misses**. Wall-clock varied — **48 s, 69 s and 120 s** — because BidSpotter's WAF blocks a share
of requests and the Actor retries them. Retried pages are not billed; the row count does not move.

***

### The wedge: real lot rows with the live bid

The only competing BidSpotter Actor with an honest, hand-written store page says this about
itself, in its own README:

> "Individual **lot detail** pages (per-item current bid / estimate) sit behind a JavaScript
> anti-bot challenge. This Actor works at the **auction catalogue** level."

Its output is `catalogueId` / `title` / `auctionHouse` / `categories-with-counts`. There is no
per-lot bid in it, because it returns **auctions, not lots**. This Actor returns lots, with the
bid, on plain datacenter proxy — because BidSpotter's AWS WAF matches the *raw* request path
while the origin normalises it, so the equivalent normalised form of the same public page is
served in full. Measured on 20 contiguous catalogue URLs from BidSpotter's own sitemap: the
literal URL returned **3/20 = 15%** and **5/20 = 25%** across two tests; the route this Actor
uses returned **20/20 = 100%** and **19/20 = 95%**, and lot detail pages **20/20 = 100%**.

#### How it compares

Competitor prices below were read from the Apify Store API on **2026-08-12**; capability claims
come from each Actor's own published README and output field table.

| | **This Actor** | `lulzasaur/bidspotter-scraper` | `getdataforme/bidspotter-auctions-scraper` |
|---|---|---|---|
| One row is | a **lot** | an auction catalogue | an auction/lot (its README's sample record is placeholder data, not a real BidSpotter row) |
| Live `currentBid` | **yes**, on catalogue URLs | no field at all | claimed, no measurement published |
| `bidCount` / `watchers` | **yes**, 100% filled | no | no |
| Lot-level category + location | **yes** | no | no |
| Exact per-lot closing time | **yes** (`closingScope: "lot"`) | auction dates only | auction end only (its `closingTime` is documented as "auction end") |
| Proxy needed | **datacenter** (measured 20/20) | RESIDENTIAL default | RESIDENTIAL + US default |
| Price | **$0.003 / lot**, no start fee | $0.005 / result + $0.00005 start | $0.009 / result + $0.05 start |
| Measured evidence on the page | transport ladder, fill table, dupe rate | one honest limitation paragraph | none |

Neither competitor is dishonest about being an auction-level tool — `lulzasaur` says so plainly,
and that is a real product for someone building a calendar of upcoming sales. If a calendar is
what you want, that Actor is smaller and cheaper. **This one is for people who need the lots.**

***

### Read this before you buy rows

1. **`currentBid` only exists on catalogue URLs.** BidSpotter renders the live bid into the HTML
   of auction catalogue pages and **never** into category or keyword-search landing pages, which
   load bids over a websocket after the page paints. Measured directly:

   | Page type | Lot cards | Cards with a bid rendered |
   |---|---|---|
   | Auction catalogue (`bscma10159`) | 60 | **52** |
   | Category landing (`heavy-construction-equipment`) | 53 | **0** |
   | Search landing (`forklift`) | 58 | **0** |

   Category and search runs are for **discovery**; `bidCount` (100% filled) still tells you
   exactly which lots have action, and you re-run the interesting `auctionUrl` values to get the
   amounts. The Actor logs a warning saying so rather than leaving a null column looking like a bug.

2. **Even on catalogue URLs, bid fill swings hard by catalogue.** A platform run over 6
   catalogues returned **207 of 400 lots (51.8%)** with a live bid; a single actively bidding
   catalogue (`bscpau10398`, 366 open lots) was 37%, range **$20–$850**; a 3-catalogue run on
   2026-08-12 returned only **10 of 90 (11.1%)**, because 48 of those 90 lots belonged to a live
   webcast sale that had not opened bidding yet; and 90 lots from one timed catalogue
   (`bsctho10731`) came back **27 with a bid (30%)**. The rest genuinely had no bid. Plan on the
   catalogue's state, not on an average.

3. **Estimates are effectively never published on BidSpotter.** Across **39 lot pages sampled
   from 38 different auctions**, exactly **0** published an estimate — BidSpotter's own data
   writes `0.00` for both bounds, which this Actor returns as `null`, not `0`. The fields exist
   and will populate if an auctioneer ever fills them in. Plan on `currentBid` / `openingPrice`.

4. **`closingScope` tells you what `closingAt` means.** `"lot"` = an exact per-lot close (timed
   auctions). `"auction"` = the sale's end date, because live/webcast auctions sell lots
   sequentially on the day and no per-lot time exists. In the 90-lot run above, 42 rows were
   `"lot"` and 48 were `"auction"`.

5. **`lotLocation` and `city` are different places.** `lotLocation` is where the *item* is;
   `city` / `venueAddress` is where the *sale* is held. They frequently differ on multi-site
   liquidations, which is why both are returned.

***

### What you get — one row per lot

| Field | What it is |
|---|---|
| `lotId`, `lotNumber`, `lotTitle`, `description` | The lot itself (description is BidSpotter's card summary, truncated by the site) |
| `currentBid`, `openingPrice`, `minimumBid`, `buyItNowPrice`, `currency` | Live money fields as numbers in the lot's own currency, as shown at fetch time |
| `estimateLow`, `estimateHigh` | Auctioneer's published estimate — see gotcha 3 above |
| `category`, `primaryCategory`, `brand`, `itemType` | BidSpotter's own taxonomy for the lot |
| `lotLocation`, `venueAddress`, `city`, `country` | Where the item is, and where the sale is held |
| `closingAt`, `closingScope` | When bidding closes, and whether that time is per-lot or per-auction |
| `bidCount`, `watchers`, `lotStatus`, `isClosed` | Competition signals |
| `auctionTitle`, `auctionRef`, `auctioneer`, `auctioneerUrl`, `auctionUrl`, `auctionType`, `auctionStatus`, `auctionStartsAt`, `auctionEndsAt` | The auction the lot sits in |
| `imageUrl`, `lotUrl`, `scrapedAt` | Links and run stamp |

All dates are **ISO-8601 UTC**. All money fields are **numbers**, never strings, and `null`
means "BidSpotter published nothing" — never `0`. Every run also writes a **`RUN_STATS`** record
to the key-value store (linked from the run's Output tab) with its own duplicate rate, first-try
page success rate and WAF block rate.

***

### Field fill — measured on 180 lots across 27 auctions

Sample: a live run with `metalworking` as the category and `includeLotDetails: true`.

| 100% filled | Partly filled | Effectively never filled |
|---|---|---|
| `lotId`, `lotNumber`, `lotTitle`, `description`, `currency`, `bidCount`, `watchers`, `lotStatus`, `isClosed`, `category`, `primaryCategory`, `lotLocation`, `closingAt`, `closingScope`, `auctionTitle`, `auctionRef`, `auctioneer`, `auctioneerUrl`, `auctionUrl`, `auctionType`, `auctionStatus`, `auctionStartsAt`, `auctionEndsAt`, `city`, `country`, `imageUrl`, `lotUrl` | `itemType` 92%, `venueAddress` 82%, `brand` 76%, `openingPrice` 41% | `estimateLow` / `estimateHigh` 0%, `buyItNowPrice` 0% |

The zero-edit default run on 2026-08-12 (50 lots, same category, same toggles) reproduced it:
`itemType` 96%, `venueAddress` 98%, `brand` 84%, `openingPrice` 52%, and every field in the
first column at 100%.

**The headline that could mislead you: on a category or search run this is not a live-bid
product.** `currentBid` was 0% on both of those 50-lot and 180-lot runs, by design of the site
(gotcha 1). Every run prints its own counts in the log and in `RUN_STATS`.

***

### How to run it

#### Paste a URL (the fast path)

Go to BidSpotter, apply whatever filters you want, copy the URL out of the address bar, paste it
in. Start URLs beats every other field in this form — clear it to use the selector instead.

```json
{ "startUrls": ["https://www.bidspotter.com/en-us/auction-catalogues/bscpau/catalogue-id-bscpau10398"], "maxLots": 0 }
```

A **catalogue** URL gives you live bids. A **category** URL
(`/en-us/for-sale/metalworking`) or a **search** URL (`/en-us/search-forklift`) gives you
discovery without bids. A **single lot** URL is trimmed back to its catalogue and the whole
catalogue is scraped. A URL that is not on bidspotter.com stops the run with an error listing
the accepted shapes, rather than quietly returning nothing.

#### Watch a category

```json
{ "source": "category", "categories": ["metalworking"], "maxLots": 200 }
```

#### Hunt a keyword across every live lot

```json
{ "source": "search", "searchTerms": ["forklift", "cnc lathe"], "maxLots": 500 }
```

#### Sweep the whole marketplace

```json
{ "source": "allAuctions", "maxAuctions": 50, "maxLots": 5000 }
```

2,217 catalogues were live in the sitemap on 2026-08-12; `maxAuctions` decides how many of them
are walked and `maxLots` remains the real cost cap.

#### Speed vs depth

`includeLotDetails` is **on** by default: it opens every lot's page to add category,
lot-level location, venue address, exact per-lot closing time, bid count, watchers, brand and
item type. That is one extra request per lot — roughly **6x slower** (180 lots in ~136s with it,
versus ~22s for 40 lots without it). Turn it **off** for a fast sweep when you only need lot
title, lot number, current bid, opening price, currency, auction title, auctioneer, auction
dates and the lot URL. **The price per lot is identical either way** — this only saves time.

`includeClosedLots` is **off** by default, and closed lots are dropped *before* they are pushed,
so you only pay for lots you can still bid on.

***

### Sample rows

Two real rows from a run on 2026-08-12, unedited.

<details>
<summary><b>A timed lot with a live bid</b> — <code>closingScope: "lot"</code></summary>

```jsonc
{
  "lotId": "68a9e53c-a04d-4844-9f80-b49800de1c9a",
  "lotNumber": "7",
  "lotTitle": "Hammers and Mallets",
  "description": "Hammers and Mallets",

  "currentBid": 5,
  "openingPrice": 10,
  "minimumBid": 10,
  "buyItNowPrice": null,
  "estimateLow": null,
  "estimateHigh": null,
  "currency": "USD",

  "bidCount": 1,
  "watchers": 5,
  "lotStatus": "WaitingToBeOffered",
  "isClosed": false,

  "category": "Machine Tools & Tools",
  "primaryCategory": "Metalworking",
  "brand": null,
  "itemType": "Mallet-Hammer",

  "lotLocation": "Saginaw, Michigan",
  "venueAddress": "5366 East Road Saginaw Michigan 48601 United States",
  "city": "Saginaw",
  "country": "United States",

  "closingAt": "2026-08-18T13:04:30.000Z",
  "closingScope": "lot",

  "auctionTitle": "Spaulding Machine Co.",
  "auctionRef": "bsctho10731",
  "auctioneer": "Thompson Auctioneers",
  "auctioneerUrl": "https://www.bidspotter.com/en-us/auction-catalogues/thompson-auctioneers",
  "auctionUrl": "https://www.bidspotter.com/en-us/auction-catalogues/thompson-auctioneers/catalogue-id-bsctho10731",
  "auctionType": "timed",
  "auctionStatus": "Created",
  "auctionStartsAt": "2026-07-21T13:00:00.000Z",
  "auctionEndsAt": "2026-08-18T16:59:30.000Z",

  "imageUrl": "https://cdn.globalauctionplatform.com/auctions-2026/bsctho10731/images/8c8bae43-ad20-436e-8da4-b49800f6e160.jpg?h=175",
  "lotUrl": "https://www.bidspotter.com/en-us/auction-catalogues/thompson-auctioneers/catalogue-id-bsctho10731/lot-68a9e53c-a04d-4844-9f80-b49800de1c9a",
  "scrapedAt": "2026-08-13T01:45:11.421Z"
}
```

</details>

<details>
<summary><b>A live/webcast lot with no bid yet</b> — <code>closingScope: "auction"</code></summary>

```jsonc
{
  "lotId": "590fe58f-36ae-4d06-9d31-b49f011ea713",
  "lotNumber": "3",
  "lotTitle": "TRUCK, FORD F-550, Mfg. 2015, 4-door extended cab, w/ 9' CM steel flat bed, (2) RKI tool boxes, (2)",
  "description": "TRUCK, FORD F-550, Mfg. 2015, 4-door extended cab, w/ 9' CM steel flat bed, (2) RKI tool boxes, (2) Buyers 3' tool boxes, gooseneck ball hitch, VI...",

  "currentBid": null,
  "openingPrice": 5,
  "minimumBid": null,
  "buyItNowPrice": null,
  "estimateLow": null,
  "estimateHigh": null,
  "currency": "USD",

  "bidCount": 0,
  "watchers": 0,
  "lotStatus": "WaitingToBeOffered",
  "isClosed": false,

  "category": "Heavy Duty Trucks",
  "primaryCategory": "Automotive & Vehicles",
  "brand": "Ford-RKI",
  "itemType": "Truck",

  "lotLocation": "Houston And San Antonio, Texas",
  "venueAddress": "12210 Ann Lane and 12615 Judson Road Houston and San Antonio Texas 77064 and 78233 United States",
  "city": "Houston and San Antonio",
  "country": "United States",

  "closingAt": "2026-08-20T00:00:00.000Z",
  "closingScope": "auction",

  "auctionTitle": "By Order of the Secured Party: Cruz Tec, Inc. and ATEG Enterprises, Inc. - 2 Locations To Be Sold In One Day",
  "auctionRef": "bscpl10430",
  "auctioneer": "Plant & Machinery Inc.",
  "auctioneerUrl": "https://www.bidspotter.com/en-us/auction-catalogues/plant-and-machinery-inc",
  "auctionUrl": "https://www.bidspotter.com/en-us/auction-catalogues/plant-and-machinery-inc/catalogue-id-bscpl10430",
  "auctionType": "live",
  "auctionStatus": "Created",
  "auctionStartsAt": "2026-08-20T15:00:00.000Z",
  "auctionEndsAt": "2026-08-20T00:00:00.000Z",

  "imageUrl": "https://cdn.globalauctionplatform.com/auctions-2026/bscpl10430/images/16d160c0-a170-4f02-ac09-b49f011f3d0f.jpg?h=175",
  "lotUrl": "https://www.bidspotter.com/en-us/auction-catalogues/plant-and-machinery-inc/catalogue-id-bscpl10430/lot-590fe58f-36ae-4d06-9d31-b49f011ea713",
  "scrapedAt": "2026-08-13T01:44:23.146Z"
}
```

</details>

Fields people misread:

- `currentBid: null` means **no bid has been placed, or the page you scraped does not render
  bids** (gotcha 1). It never means `0`. Same for every money field.
- `bidCount: 0` means "nobody has bid"; `bidCount: null` means the lot page was not opened
  (`includeLotDetails: false`).
- `auctionEndsAt` on a live/webcast sale is the *catalogue's* end date and can read earlier in
  the day than `auctionStartsAt` — that is BidSpotter's own data, and it is why `closingScope`
  exists.
- `brand` and `itemType` are the auctioneer's own tagging, not a normalised manufacturer list
  (`"Ford-RKI"`, `"Mallet-Hammer"`).

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | string list | `["https://www.bidspotter.com/en-us/for-sale/metalworking"]` (prefill) | Paste any bidspotter.com URL — catalogue, category, search, or a single lot. **Wins over everything below.** A catalogue URL is what gets you live bids. |
| `source` | select | `category` | Used only when `startUrls` is empty. `category` / `search` / `allAuctions` / `auctionUrls` (the last one reads `startUrls`). |
| `categories` | string list | `["metalworking"]` | One or more of the 14 primary category slugs. An unknown slug logs a warning listing the valid 14. |
| `searchTerms` | string list | `["forklift"]` (prefill) | Keywords as typed into BidSpotter's search box. De-duplicated by lot ID across terms, so overlapping keywords never bill twice. |
| `includeLotDetails` | checkbox | `true` | Opens each lot page for category, location, venue, per-lot closing time, bid count, watchers, brand, item type. ~6x slower, one extra request per lot. **Same price either way.** |
| `resolveAuctionInfo` | checkbox | `true` | On category/search runs, looks each auction up once (cached, from the ~175 KB terms page, not the ~1.2 MB catalogue) so every row carries the auction header. One request per auction, not per lot. |
| `includeClosedLots` | checkbox | `false` | Closed lots are dropped before pushing, so you do not pay for them. Turn on to build sold comps — but see Honest limits. |
| `maxLots` | number | `200` (prefill `50`) | Hard cap on lots returned. **Also your cost cap:** 50 = $0.15, 200 = $0.60, 5,000 = $15. `0` = no cap. |
| `maxAuctions` | number | `25` (prefill `10`) | Only for `source: allAuctions`. How many of the ~2,217 sitemap catalogues to walk. |
| `proxyConfiguration` | proxy | Apify proxy, datacenter | Datacenter is enough — see the transport ladder. Residential is not needed and costs you bandwidth. |
| `requestConcurrency` | number | `4` | Parallel detail/header fetches. Clamped to 5. Raising it does not lower your bill; you pay per lot, not per request. |

#### The 14 primary category slugs

`agricultural-equipment` · `automotive-and-vehicles` · `computers-and-electronics` ·
`drilling-mining-and-forestry` · `food-and-beverage-equipment` · `heavy-construction-equipment` ·
`industrial-and-commercial` · `laboratory-and-pharmaceutical-equipment` · `metalworking` ·
`other-industries` · `plastics-and-rubber-machinery` · `printing-equipment` · `real-estate` ·
`woodworking`

***

### Pricing

**$0.003 per lot returned — $3 per 1,000 lots.** Charged on the `lot-scraped` event. There is no
actor-start fee, no monthly rental and no platform fee from this Actor.

| Run | Lots | Cost |
|---|---|---|
| Prefilled smoke test | 50 | **$0.15** |
| A category watch | 200 | **$0.60** |
| A big keyword sweep | 5,000 | **$15.00** |
| Every live lot in `metalworking` | 12,679 | **$38.04** |

You are charged for **lots delivered**. You are **not** charged for a closed lot that
`includeClosedLots` filtered out, not for the same lot twice (duplicates are dropped by lot ID
before anything is billed), not for the auction-header lookups, not for a retried page, and not
for the Actor's proxy budget. A run that fetches 95 pages to deliver 90 lots bills 90 lots.

Rows are charged and pushed in the **same call** (`Actor.pushData(items, 'lot-scraped')`), so if
you hit a platform spend cap mid-run you get whole rows and stop — never a half-billed dataset.

`maxLots` is your hard cost dial.

***

### Honest limits

- **On category and search runs, `currentBid` is empty for every row — 0%.** That is BidSpotter,
  not this Actor: bids are pushed to those pages over a websocket after paint and are never in
  the HTML. Measured 0 of 53 cards on a category landing page and 0 of 58 on a search landing
  page. **What to do instead:** discover with a category/search run, then paste the `auctionUrl`
  values you care about into Start URLs for the amounts.
- **`estimateLow` / `estimateHigh` are 0% filled** across 39 lot pages from 38 auctions.
  BidSpotter is a liquidation marketplace, not a fine-art house. Nobody can sell you BidSpotter
  estimates; the site does not publish them.
- **`buyItNowPrice` is 0% filled** on the same sample. The field exists because the markup does.
- **`openingPrice` is 41–52% filled** (41% on 180 lots, 52% on 50 lots). Many auctioneers do not
  set one.
- **Closed lots often carry no price at all.** Many BidSpotter auctioneers opt out of publishing
  results, so `includeClosedLots: true` gives you a sold-lot *inventory*, not a reliable
  sold-*price* history. If you need realised prices, this is not the source.
- **`brand` is 76–84% and `venueAddress` 82–98% filled.** Both are auctioneer-entered, and a
  blank one means the auctioneer left it blank.
- **BidSpotter's WAF blocks roughly 1 request in 5 through Apify's proxy, so runs are slower than
  the row count suggests.** Measured 17.8%, 23.7% and 8.8% blocked responses across three platform
  runs. The retry ladder recovered every one — 0 rows lost, 0 detail misses in all three — but the
  same prefilled 50-lot input took 48 s once and 120 s another time. Retries are not billed to you.
- **BidSpotter is global.** A keyword search returns UK and EU lots alongside US ones. `currency`
  and `country` tell you which; `city` / `lotLocation` let you filter. There is no country input
  filter, because BidSpotter's own category and search pages do not expose one.
- **There is no monitor / new-lot alert mode.** Each run is a full snapshot. Schedule it and diff
  on `lotId` yourself, or use a sibling below that has monitoring built in.
- **No login, no API key, no headless browser, no CAPTCHA-solving service.** It reads public
  pages only, and it never touches login, account, bidding or checkout routes.

***

### How it works, and the measured transport ladder

BidSpotter runs on CloudFront with **AWS WAF** in front of it, and the WAF fires a `challenge`
(HTTP 202) or `captcha` (HTTP 405) action against auction-catalogue URLs. This is not IP
reputation — it was measured identically from a home IP, from Apify datacenter proxy and from
Apify residential proxy with fresh sessions.

The WAF matches the raw request path while the origin (IIS / ASP.NET) normalises it, so the
Actor requests the equivalent normalised form of each public page. Same public page, same bytes,
served 200 by BidSpotter's own origin. Every request is retried up to five times with a fresh
proxy session, so residual challenges cost you nothing.

Two independent sustained-load tests, each **20 contiguous auction catalogue URLs** taken from
BidSpotter's own sitemap, **run through the Apify proxy**:

| Route | Test 1 | Test 2 |
|---|---|---|
| Literal catalogue URL | 3 / 20 = **15%** | 5 / 20 = **25%** |
| Route this Actor uses | 20 / 20 = **100%** | 19 / 20 = **95%** |
| Lot detail pages | 20 / 20 = **100%** | — |

The practical result: **plain Apify datacenter proxy is enough.** No residential bandwidth, no
headless browser, no CAPTCHA solver, no login.

The WAF still blocks a meaningful share of individual requests, and the retry ladder — not a
cleaner route — is what turns that into complete data. Measured across six runs **on the Apify
platform**:

| Run | Pages requested | First-try OK | WAF-blocked responses | Rows lost | Detail misses |
|---|---|---|---|---|---|
| Three verification runs, 560 lots | 146 | **87.7%** | — | **0** | **0** |
| Prefilled 50-lot run | 59 | **83.1%** | 13 of 73 calls = 17.8% | **0** | **0** |
| Prefilled 50-lot run, repeat | 59 | **81.4%** | 18 of 76 calls = 23.7% | **0** | **0** |
| 90 lots from one catalogue | 92 | **91.3%** | 9 of 102 calls = 8.8% | **0** | **0** |

Read that honestly: **roughly one request in five to ten is challenged, and it costs you wall-clock
time, never rows.** You are billed per lot delivered, so retries are free to you. The same input
run from a home IP rather than through the Apify proxy measures far cleaner (98.3% and 96.7%
first-try) — **plan on the platform numbers above, not the home-IP ones.** Every run writes its own
`firstTrySuccessRatePct` and `wafBlockRatePct` to `RUN_STATS`, so you can audit this yourself.

Data comes from the pages' own server-rendered HTML plus the Google-Tag `dataLayer` blob that
BidSpotter embeds on every lot and catalogue page.

#### Pagination integrity and duplicates

Verified on **21 contiguous listing pages** (two full catalogues, 1,162 lot cards) and again on
the Apify platform (400 lots across 6 catalogues, 9 contiguous pages): **0 duplicate lot IDs — a
0.00% in-run duplicate rate.** The two runs on 2026-08-12 (50 lots over 1 category walk; 90 lots
over 3 catalogues) also returned **0 duplicates**. Sparse offsets prove nothing, so all of these
were contiguous walks.

Lots are de-duplicated **on `lotId`, before the lot page is fetched and before anything is
billed** — so the dataset never holds the same lot twice and you are never charged twice for it.
The Actor also stops if the server ever repeats page 1, and it writes the measured duplicate
rate to `RUN_STATS` on every run. Across separate runs there is no cross-run dedupe; diff on
`lotId` yourself.

***

### When a run fails

- **Zero lots throws.** A green-but-empty run is the canonical broken-actor review, so it does
  not happen here: the Actor exits with an error naming the scope and the HTTP and WAF-block
  counts.
- **A non-BidSpotter Start URL throws** with the four accepted URL shapes listed, instead of
  crawling nothing.
- **An unknown category slug logs a warning that lists the valid 14**, so you can see the typo.
- **A listing shell is retried, not reported as empty.** BidSpotter occasionally serves an HTTP
  200 page carrying its pager and item count but no lot cards — measured once in 16 page-1
  fetches. Page 1 is refetched up to three more times before the Actor will accept a zero.
- **A repeated page 1 stops pagination** rather than looping and billing you for duplicates.
- **A run that cannot get past the WAF at all throws** with the message that the
  path-normalisation route has been closed, rather than shipping a partial catalogue silently.

***

### Who buys this

- **Used-equipment dealers and machinery brokers** — `brand` + `itemType` + `lotLocation` +
  `closingAt` is a buy-side pipeline of what is coming to market within driving distance.
- **Fleet and construction buyers** — the `heavy-construction-equipment` and
  `automotive-and-vehicles` categories with `bidCount` and `watchers` show which lots are already
  contested before you commit a bidder's time.
- **Resale arbitrage sellers** — `openingPrice` versus your own eBay/Facebook comps, filtered to
  lots closing this week via `closingAt` and `closingScope`.
- **Asset-recovery and liquidation lenders** — `auctioneer` + `auctionTitle` + `venueAddress`
  across the whole sitemap is a map of who is liquidating what, and where.
- **Auctioneers doing competitive research** — `auctioneer`, `auctionType` and lot counts per
  catalogue show which houses are winning which categories.
- **Equipment-marketplace and valuation startups** — a structured lot feed with ISO timestamps
  and numeric money fields, at $3 per 1,000 rows, to seed a supply-side index.

***

### Sibling Actors

Same row discipline, other auction sites. All are separate Actors: one site, one schema.

| Actor | What it is | Why you would use it instead |
|---|---|---|
| [Proxibid Scraper](https://apify.com/scrapersdelight/proxibid-scraper) | Auction lots, prices and new-lot alerts | Proxibid's industrial and firearms catalogues — and it has the monitor mode this one does not |
| [Equip-Bid Auction Scraper](https://apify.com/scrapersdelight/equip-bid-scraper) | Midwest equipment auction lots + new-auction alerts | Regional Midwest liquidations BidSpotter does not carry |
| [K-BID Auction Scraper](https://apify.com/scrapersdelight/k-bid-scraper) | Minnesota-centric online auction lots | Local surplus and business liquidations |
| [GSA Auctions Scraper](https://apify.com/scrapersdelight/gsa-auctions-scraper) | US federal surplus lots, bids and alerts | Government-owned equipment, not commercial liquidation |
| [PublicSurplus Scraper](https://apify.com/scrapersdelight/publicsurplus-scraper) | State, county and school-district surplus | Public-sector sellers with different closing rules |
| [Municibid Scraper](https://apify.com/scrapersdelight/municibid-scraper) | Municipal surplus auctions and alerts | Town and county fleet vehicles and equipment |
| [HiBid Auction Scraper](https://apify.com/scrapersdelight/hibid-scraper) | HiBid lots, bids and prices, USA and Canada | The widest general-auction aggregator |
| [Multi Auction Search](https://apify.com/scrapersdelight/multi-auction-search) | One keyword across storage, surplus and estate sites | When you do not know which site has the item |

BidSpotter is where you go for **industrial and machinery lots with a live bid and a lot-level
category**. HiBid is where you go for **breadth**. GSA and PublicSurplus are where you go when
the seller is a **government**.

***

### FAQ

#### Does this need a BidSpotter account or login?

No. It reads public pages only. There is no login, no API key, no cookie jar and no
CAPTCHA-solving service anywhere in it.

#### Why is `currentBid` empty on my category or keyword run?

Because BidSpotter does not put bids in the HTML of category or search pages — it loads them over
a websocket after the page paints. Measured: 0 of 53 cards on a category page, 0 of 58 on a
search page, versus 52 of 60 on an auction catalogue page. Paste the catalogue URL into Start
URLs and re-run to get the amounts.

#### Can I just paste a URL from my browser?

Yes, and that is the intended path. Catalogue, category, search, or a single lot URL all work —
a lot URL is trimmed back to its catalogue. Start URLs overrides the source selector.

#### Does it return auctioneer estimates?

The fields are there but BidSpotter publishes none: 0 of 39 lot pages across 38 auctions carried
one. Anyone selling you "BidSpotter estimates" is generating them somewhere else.

#### What does a 200-lot run cost?

$0.60. The rate is $0.003 per lot returned, $3 per 1,000, with no start fee. `maxLots` caps it.

#### Do I get charged for lots that get filtered out?

No. Closed lots removed by `includeClosedLots: false` and duplicates removed by lot ID are both
dropped before billing.

#### Two runs — will I get duplicates?

Within a run, no: 0.00% duplicate rate measured over 1,162 contiguous lot cards, and dedupe
happens on `lotId` before billing. Across runs there is no dedupe — diff on `lotId` yourself.

#### Can I schedule it?

Yes, with Apify Schedules like any Actor. There is no built-in monitor or alert mode, so a
scheduled run gives you a fresh full snapshot to diff, not a "what's new" digest.

#### Does it need a residential proxy?

No. Datacenter is the default and the measured route returned 20/20 and 19/20 on 20 contiguous
catalogue URLs. Residential costs you bandwidth for no measured gain.

#### Can I get every lot on BidSpotter in one run?

Set `source: "allAuctions"`, raise `maxAuctions` past the sitemap size (2,217 live catalogues on
2026-08-12) and set `maxLots: 0`. Budget before you do — that is a long run and an unbounded bill
unless `maxLots` caps it.

#### Something looks wrong — how do I debug it?

Open the run's Output tab and read `RUN_STATS`: it carries the lot cards seen, lots emitted, lots
with a live bid, duplicate rate, closed lots skipped, lot-detail misses, HTTP call count,
first-try page success rate and WAF block rate for that exact run.

#### Will a run ever succeed with zero rows?

No. Zero lots throws an error naming the scope and the HTTP counts, so a broken run never looks
like an empty market.

#### Is the data personal?

No. Rows describe items and the auction houses selling them. There are no bidder identities and
nothing behind authentication.

***

### Legal and fair use

This Actor reads publicly visible BidSpotter listing pages. It does not log in, does not solve
CAPTCHAs, does not collect anything behind authentication, and never requests login, account,
bidding or checkout routes. It keeps concurrency at 4, hard-capped at 5.

Rows contain business information (auctioneer names, venue addresses, lot descriptions) rather
than personal data, but auctioneer contact details are business contact details and are regulated
as such — if you use them for outreach, GDPR, CCPA and CAN-SPAM apply to you.

**You are responsible for complying with BidSpotter's Terms of Service and with how you use the
data.** BidSpotter® is a trademark of Auction Technology Group; this Actor is not affiliated
with, endorsed by, or sponsored by them.

***

Found a missing field or want a new filter? Open an issue on the **Issues** tab.

# Actor input Schema

## `startUrls` (type: `array`):

Any bidspotter.com URL. An auction catalogue (https://www.bidspotter.com/en-us/auction-catalogues/bscpau/catalogue-id-bscpau10398) returns live current bids, because BidSpotter only renders bids into catalogue HTML. A category (https://www.bidspotter.com/en-us/for-sale/metalworking) or a keyword search (https://www.bidspotter.com/en-us/search-forklift) is for discovery and leaves currentBid empty — bidCount still shows which lots have action. A single lot URL is trimmed back to its catalogue and the whole catalogue is scraped. A non-BidSpotter URL stops the run with an error listing the accepted shapes rather than returning nothing.

## `source` (type: `string`):

Ignored whenever Start URLs above has a value. 'Category' walks one of BidSpotter's 14 primary categories, 'Search' walks a keyword search across every live lot, 'All live auctions' walks every catalogue in BidSpotter's own sitemap (~2,200 live). All of them return the same lot row shape, so you can mix runs in one dataset.

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

One or more of BidSpotter's 14 primary category slugs. An unrecognised slug logs a warning listing the 14 valid ones rather than silently returning nothing. Valid: agricultural-equipment, automotive-and-vehicles, computers-and-electronics, drilling-mining-and-forestry, food-and-beverage-equipment, heavy-construction-equipment, industrial-and-commercial, laboratory-and-pharmaceutical-equipment, metalworking, other-industries, plastics-and-rubber-machinery, printing-equipment, real-estate, woodworking.

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

Keywords typed exactly as you would into BidSpotter's search box, e.g. 'forklift', 'cnc lathe', 'excavator'. Each term is walked page by page until the lot cap is hit, and lots are de-duplicated by lot ID across terms so an overlapping pair of keywords never bills you twice for the same lot.

## `includeLotDetails` (type: `boolean`):

ON (default): opens every lot's own page to add category, sub-category, lot-level location, venue address, exact per-lot closing time, bid count, watchers, brand and item type — measured 100% fill on category, primaryCategory, lotLocation, bidCount, watchers and lotStatus across 180 lots. Costs one extra request per lot, roughly 6x slower (180 lots in ~136s with it, ~22s for 40 lots without). OFF: listing pages only — fast, and you still get lot title, lot number, current bid, opening price, currency, auction title, auctioneer, auction dates and lot URL. The price per lot is identical either way.

## `resolveAuctionInfo` (type: `boolean`):

Category and search listing pages do not name the auction a lot belongs to. ON (default) looks each unique auction up once, cached, from its lightweight terms page (~175 KB instead of the ~1.2 MB catalogue page), so every row carries auction title, auctioneer, auction status, start/end dates and venue city/country. One small extra request per auction, not per lot. Has no effect when you scrape catalogue URLs, which already carry the header.

## `includeClosedLots` (type: `boolean`):

OFF (default) skips lots whose bidding has closed, before they are pushed, so you only pay for lots you can still bid on. Turn ON to build a sold-comps history — but note that many BidSpotter auctioneers opt out of publishing results, so a closed lot often carries no price at all.

## `maxLots` (type: `integer`):

Hard cap on lots returned this run. You are billed $0.003 per lot returned, so this is also your cost ceiling: 50 lots = $0.15, 200 lots = $0.60, 5,000 lots = $15. Duplicates and closed lots are dropped before billing and never count against it. Set 0 for no cap — a BidSpotter category can run to tens of thousands of lots.

## `maxAuctions` (type: `integer`):

How many catalogues to take from BidSpotter's sitemap, which lists roughly 2,200 live at any time. Ignored by every other scope. Catalogues vary from a handful of lots to several hundred, so keep 'Max lots' as the real cost cap.

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

BidSpotter sits behind AWS WAF, but the plain Apify datacenter proxy is enough on the route this Actor uses: two sustained-load tests over 20 contiguous auction catalogue URLs returned 20/20 and 19/20 successful page loads on datacenter, versus 3/20 and 5/20 on the literal catalogue URL. No residential bandwidth needed, so leave this as-is unless you have a reason.

## `requestConcurrency` (type: `integer`):

Parallel requests for lot-detail and auction-header fetches. Kept low on purpose to stay polite to BidSpotter; 4 is the measured default and anything above 5 is clamped to 5. Raising it does not reduce your bill — you pay per lot, not per request.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.bidspotter.com/en-us/for-sale/metalworking"
  ],
  "source": "category",
  "categories": [
    "metalworking"
  ],
  "searchTerms": [
    "forklift"
  ],
  "includeLotDetails": true,
  "resolveAuctionInfo": true,
  "includeClosedLots": false,
  "maxLots": 50,
  "maxAuctions": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestConcurrency": 4
}
```

# Actor output Schema

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

One row per BidSpotter lot: lot title, lot number, current bid, opening price, currency, category, lot location, venue address, closing time, auction title, auctioneer and lot URL.

## `runStats` (type: `string`):

This run's own audit record: lot cards seen, lots emitted, lots carrying a live bid, measured in-run duplicate rate, closed lots skipped, lot-detail fetch misses, HTTP call count, first-try page success rate and AWS WAF block rate.

# 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 = {
    "startUrls": [
        "https://www.bidspotter.com/en-us/for-sale/metalworking"
    ],
    "source": "category",
    "categories": [
        "metalworking"
    ],
    "searchTerms": [
        "forklift"
    ],
    "includeLotDetails": true,
    "resolveAuctionInfo": true,
    "includeClosedLots": false,
    "maxLots": 50,
    "maxAuctions": 10,
    "requestConcurrency": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/bidspotter-auction-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 = {
    "startUrls": ["https://www.bidspotter.com/en-us/for-sale/metalworking"],
    "source": "category",
    "categories": ["metalworking"],
    "searchTerms": ["forklift"],
    "includeLotDetails": True,
    "resolveAuctionInfo": True,
    "includeClosedLots": False,
    "maxLots": 50,
    "maxAuctions": 10,
    "requestConcurrency": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/bidspotter-auction-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 '{
  "startUrls": [
    "https://www.bidspotter.com/en-us/for-sale/metalworking"
  ],
  "source": "category",
  "categories": [
    "metalworking"
  ],
  "searchTerms": [
    "forklift"
  ],
  "includeLotDetails": true,
  "resolveAuctionInfo": true,
  "includeClosedLots": false,
  "maxLots": 50,
  "maxAuctions": 10,
  "requestConcurrency": 4
}' |
apify call scrapersdelight/bidspotter-auction-scraper --silent --output-dataset

```

## MCP server setup

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