# AllSurplus Scraper — Industrial Surplus Auction Lots (`scrapersdelight/allsurplus-industrial-lot-scraper`) Actor

$4.50 per 1,000 lots, no start fee. Every live COMMERCIAL surplus lot on AllSurplus: the named corporate seller, its pickup street address, closing time, current bid, buyer's premium, condition, quantity, specs and photos. Government surplus is excluded — our GovDeals Scraper covers that.

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

## Pricing

$4.50 / 1,000 per commercial surplus 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/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

## 🏭 AllSurplus Scraper — Industrial & Commercial Surplus Auction Lots

**Every live *commercial* surplus lot on [AllSurplus](https://www.allsurplus.com), with the named
corporate seller and the pickup street address on every single row.**

Shell, Schneider Electric, HP, Seagate, Agilent, ArcelorMittal, Bristol Myers Squibb, Tyson and 120+
other companies sell their retired plant, machinery, lab kit and IT hardware here. This Actor returns
each lot as one clean row: what it is, what it is worth right now, when bidding closes, **which
company is selling it**, and **the street address it has to be collected from**.

> ### ⚠️ This Actor deliberately returns **no government surplus**
>
> allsurplus.com is the storefront for **two different corpora**, and its own API separates them with
> one field, `businessId`. Measured live on **2026-09-18**:
>
> | `businessId` | corpus | open lots | covered by |
> |---|---|---:|---|
> | `GI` | **GoDove — commercial/industrial surplus** | **2,892** | 👉 **this Actor** |
> | `GD` | GovDeals — government surplus | 26,705 | [`scrapersdelight/govdeals-auction-scraper`](https://apify.com/scrapersdelight/govdeals-auction-scraper) |
> | `AD` | the AllSurplus aggregate of both | 29,366 | — |
>
> The two Actors **complement** each other: one covers the commercial half, the other the
> government half, and neither resells the other's rows. A scraper pointed at the aggregate
> returns mostly GovDeals lots — on the aggregate's first page, **85 of 100 rows were `GD`**.
>
> `businessId=GI` is a **hard-coded constant** here. No input can change it, it is sent as a
> **server-side** filter so GovDeals rows never even reach the wire, every row is checked again on
> the way out, and every delivered row carries `business_id` so you can verify the scope yourself.

***

### 🔎 What does AllSurplus Scraper do?

- 🏢 **Names the corporate seller on every row** — `seller_company` was filled on **292 of 292**
  captured rows (100%), and on **2,892 of 2,892** on a full catalogue walk. Real company names, not
  `Seller 12345` placeholders (0 placeholders in 292 rows, 26 distinct sellers).
- 📍 **Gives the pickup street address on every row** — `pickup_address` 100% filled, 99.3% of them
  containing a street number. Buyers must inspect and remove, so Liquidity Services publishes where
  the equipment physically sits.
- ⏱️ **Real closing times in real UTC** — `end_date` 100% filled. The site's bare timestamps are
  US/Eastern wall-clock *even on a lot in Denmark*; this Actor derives the offset from each record
  instead of hard-coding EST/EDT, and hands you both.
- 💰 **The money, complete** — current bid, opening bid, bid count, **bid increment** and
  **buyer's premium %** (12–18% on this corpus), all **100% filled** on a live 300-lot run. The
  search API does not carry an increment at all (0 of 292 captured rows) — it comes from the live
  bid box, which this Actor calls for you.
- 🔧 **Structured spec attributes** — `specs` is a label/value list straight off the lot
  (Manufacturer, Model, Serial Number, Year of Manufacture, Condition, Lot Number, Inventory ID),
  populated on **300 of 300** rows of a live 300-lot run.
- 📄 **Seller spec sheets** — `attachments` turns the seller's own uploaded PDFs/spreadsheets into
  resolvable URLs. Genuinely sparse — 4.3% of a live 300-lot run carried one.
- 🖼️ **The whole photo gallery** — the search grid gives you one thumbnail; the detail call gives
  the full set (up to 366 photos on one captured lot).
- 🌍 **Global, and it tells you the currency** — lots in **six currencies** (USD, EUR, ZAR, CNY, AUD,
  GBP) across 15 countries. USA 1,150 · Germany 490 · Israel 405 · South Africa 331 · Netherlands 103
  · Poland 94 · China 80 · Australia 67 · UK 67 · Denmark 43 · Belgium 29 · Spain 11 · South Korea 6
  · Mexico 5 · France 2.
- 🗂️ **Sale-event context** — industrial surplus sells in events ("Wizpaper – Corrugated Cardboard
  Factory", "ArcelorMittal Sale 23"). `sale_event_title` and `sale_event_id` are on every enriched row.
- 🔔 **Monitor mode** — run it on a schedule and get only the lots that are new since last time, with
  Slack / webhook / email alerts.

### 👤 Who is this for?

| Buyer | What they do with a row |
|---|---|
| Industrial liquidators & used-machinery dealers | Source inventory; `specs` + `condition` + `buyer_premium_pct` price a bid before the hammer |
| IT-asset disposition (ITAD) firms | Track Seagate/HP/Agilent lots and the site they must be collected from |
| Exporters & freight forwarders | `pickup_address` + `country` + `end_date` = a quotable collection job |
| Procurement / capex teams | Buy the same asset used, at a named plant, with the removal terms attached |
| B2B prospectors | A company disposing of plant is a *trigger event* — `seller_company` + `pickup_address` is the account and the site |

***

### 📤 Output — one row per lot

Real row, reproduced from the captured bytes in `fixtures/` (long text and the image list
abbreviated with an ellipsis; the offline validator checks the abbreviations are real prefixes of
the real values):

```json
{
  "lot_id": "80-29964",
  "asset_id": 80,
  "account_id": 29964,
  "auction_id": 1,
  "business_id": "GI",
  "lot_url": "https://www.allsurplus.com/asset/80/29964",
  "title": "Escher Wyss Corrugating Medium Board Paper Production Line",
  "description": "Escher Wyss Corrugating Medium Board Paper Production Line\n\nComplete corrugating medium/board paper production line\n\nInstallation on 2 levels\nLine length approximately 110 m - Line …",
  "category": "Complete Lines, Industrial",
  "category_code": "28P",
  "sale_type": "Make Offer",
  "status": "active",
  "audience": "General Public",
  "currency": "EUR",
  "current_bid": 100000,
  "opening_bid": 100000,
  "total_bids": 1,
  "has_bid": true,
  "high_bidder_id": null,
  "reserve_met": true,
  "seller_company": "MCEI Demolition",
  "seller_account_id": 29964,
  "pickup_address": "Rue du Chocquet",
  "pickup_address_2": null,
  "city": "Wizernes",
  "state_code": "FR-O",
  "state_name": "Hauts-de-France",
  "zipcode": "62570",
  "country": "France",
  "country_code": "FRA",
  "latitude": null,
  "longitude": null,
  "start_date": "2026-08-12T12:34:00.000Z",
  "end_date": "2026-09-18T22:00:00.000Z",
  "end_date_local": "2026-09-18T18:00:00",
  "end_date_display": "September 18, 2026 06:00 PM EST",
  "lot_number": "1",
  "inventory_id": null,
  "make": "Escher Wyss",
  "model": null,
  "model_year": "1986",
  "thumbnail": "https://webassets.lqdt1.com/assets/photos/29964/29964_80_ab75997b-3ff9-4c4e-9d5c-546bb71f806f.jpg?cb=260812082542",
  "search_scope": "all open commercial lots",
  "quantity": 1,
  "quantity_unit": "Each",
  "quantity_available": 1,
  "condition": "Used/See Description",
  "condition_code": "SD",
  "vin_serial": null,
  "specs": [
    { "label": "Manufacturer", "value": "Escher Wyss" },
    { "label": "Year of Manufacture", "value": "1986" },
    { "label": "Condition", "value": "Used/See Description" },
    { "label": "Lot Number", "value": "29964-80" }
  ],
  "attachments": [],
  "weight": null,
  "weight_unit": null,
  "sale_event_id": 31001,
  "sale_event_title": "Wizpaper - Corrugated Cardboard Factory",
  "category_path": "Industrial, Commercial Equipment > Complete Plants and Lines, Industrial > Complete Lines, Industrial",
  "parent_category": "Complete Plants and Lines, Industrial",
  "asset_class": null,
  "seller_type": "Commercial Seller",
  "seller_page_url": null,
  "marketplace_contact_name": "EMEA Customer Services",
  "marketplace_contact_email": "LSI-EMEA@liquidityservices.com",
  "marketplace_contact_phone": "+3302070983792",
  "inspection_terms": "Items offered for sale are sold “as is / where is” subject to sale terms and conditions. M …",
  "payment_terms": "Payment instructions: Payments must be made by bank transfer unless otherwise stated. To o …",
  "pickup_terms": "Items may be removed by appointment only. All items must be removed within the removal per …",
  "special_terms": "Please note: If you are the winning bidder and default on your purchase in any way, your a …",
  "images": [
    "https://webassets.lqdt1.com/assets/photos/29964/29964_80_ab75997b-3ff9-4c4e-9d5c-546bb71f806f.jpg?cb=260812082542",
    "https://webassets.lqdt1.com/assets/photos/29964/29964_80_124cebb1-053c-4822-854c-97b978931dbc.jpg?cb=260812082544",
    "… 9 more"
  ],
  "image_count": 11,
  "is_cross_listed": true,
  "detail_fetched": true,
  "bid_increment": 1500,
  "buy_now_price": null,
  "buyer_premium_pct": 18,
  "has_reserve": false,
  "auto_extension": false,
  "view_count": 96,
  "visitor_count": 53,
  "watcher_count": 1,
  "bid_box_fetched": true
}
```

#### Every field, and how often it is really filled

**Fill %** below is measured against the **raw bytes**, not against the parser, and comes from three
real measurements on 2026-09-18: a full **2,892-lot catalogue walk**, a **300-lot live platform run**
(run `NrTaIqa05bEIVRNhu`, where 300 of 300 lots got both enrichment calls), and a 65-lot enrichment
sample. Where a number is slice-dependent, both are given — e.g. `pickup_address_2` is 38.6% across
the whole catalogue but 5.0% on the closing-soonest 300, which are 283/300 US lots. A *filtered* run
will differ from these, and that is the source varying, not the parser.

##### Identity — on every row

| Field | Type | Fill | What it is |
|---|---|---:|---|
| `lot_id` | string | 100% | `"<asset_id>-<account_id>"` — stable, unique, the key to dedupe on |
| `asset_id` | int | 100% | AllSurplus asset id |
| `account_id` | int | 100% | Seller account id (the second number in a lot URL) |
| `auction_id` | int | 100% | The auction **round**. An unsold lot is re-listed under a new round |
| `business_id` | string | 100% | Always `"GI"`. Your proof that no GovDeals row got in |
| `lot_url` | string | 100% | The lot page on allsurplus.com |
| `lot_number` | string | 96.6% | Seller's own lot number. The `-1` "no lot number" sentinel is `null`, never `"-1"` |
| `inventory_id` | string | 93.5% | Seller's internal inventory reference |
| `search_scope` | string | 100% | Which of your scopes produced this row |
| `scraped_at` | ISO | 100% | When we read it |

##### The seller and where the equipment sits — the reason this Actor exists

| Field | Type | Fill | What it is |
|---|---|---:|---|
| `seller_company` | string | **100%** | The selling company, by name |
| `seller_account_id` | int | 100% | Its AllSurplus account id — feed it back in as `sellerAccountIds` for everything they list |
| `seller_type` | string | 100%ᴿ | `"Commercial Seller"` on 300 of 300 live rows and 65 of 65 sampled lots. The government-seller filter returns **0** lots in this corpus |
| `pickup_address` | string | **100%** | Street address the lot must be collected from |
| `pickup_address_2` | string | 38.6%ᶜ / 5.0%ᴿ | Second address line where the seller published one. Common outside the US, rare inside it |
| `city` | string | 100% | |
| `state_code` | string | 94.5% | `TX`, `DE-SN`, `FR-O`. `null` where the country has no sub-region (the API writes `DNK-NA`) |
| `state_name` | string | 94.5% | `Texas`, `Hauts-de-France` |
| `zipcode` | string | 99.7%ᶜ | |
| `country` | string | 100% | Full name, e.g. `United States of America`. A **keyword** search is the one scope where AllSurplus omits the country name from its response (0 of 20 rows, measured twice) — there it is decoded from the ISO-3 code, which is always present, using names taken from the scopes that do publish them. An unrecognised code gives `null` rather than a guess |
| `country_code` | string | 100% | ISO-3, e.g. `USA` |
| `latitude` / `longitude` | float | 53.3%ᶜ / 74.7%ᴿ | Never a fabricated `0,0` — absent means `null` |
| `seller_page_url` | string | 17%¹ | The seller's microsite on AllSurplus where one exists |
| `marketplace_contact_*` | string | 91.7%ᴿ / 95%¹ | See the terms table below — it is **not** the seller company's contact |

##### The lot

| Field | Type | Fill | What it is |
|---|---|---:|---|
| `title` | string | 100% | |
| `description` | string | 100% | HTML stripped to readable text |
| `category` | string | 100% | e.g. `Complete Lines, Industrial` |
| `category_code` | string | 100% | AllSurplus' own code, usable as a `categories` input |
| `parent_category` | string | 58%¹ | |
| `category_path` | string | 100%¹ | Full breadcrumb chain |
| `asset_class` | string | 57%¹ | Seller's asset class label |
| `make` | string | 68.8% | |
| `model` | string | 56.8% | |
| `model_year` | string | 34.6%ᶜ | |
| `vin_serial` | string | 57.3%ᴿ / 46%¹ | VIN or serial where the seller published one |
| `quantity` | int | 100%¹ | |
| `quantity_unit` | string | 100%¹ | `Each`, `Lot`, `Pallet` … |
| `quantity_available` | int | 100%¹ | |
| `condition` | string | 100%¹ | `New/Unused`, `Used/See Description` … |
| `condition_code` | string | 100%¹ | The raw code. A trailing-space code (`"N "`) is trimmed before lookup |
| `specs` | array | 100%ᴿ | `[{label, value}]` — Manufacturer, Model, Serial Number, Year, Condition, Lot Number, Inventory ID. 300 of 300 live rows |
| `attachments` | array | 4.3%ᴿ / 9%¹ | `[{title, file_name, url}]` — the seller's own spec PDFs/spreadsheets. Genuinely sparse |
| `weight` / `weight_unit` | number / string | 1.0%ᴿ / 3%¹ | Published on very few lots. Kept, not advertised |
| `images` | array | 100%¹ | Full gallery, absolute URLs |
| `image_count` | int | 100%¹ | |
| `thumbnail` | string | 100% | First image; present even without the detail call |
| `is_cross_listed` | bool | 100%¹ | Whether the same lot also appears on another Liquidity Services marketplace |

##### The money and the clock

| Field | Type | Fill | What it is |
|---|---|---:|---|
| `currency` | string | 100% | USD, EUR, ZAR, CNY, AUD or GBP — **per lot** |
| `current_bid` | number | 100% | |
| `opening_bid` | number | 100% | |
| `total_bids` | int | 100% | `0` is a real answer and is delivered as `0` (206 of 292 captured rows had no bids yet) |
| `has_bid` | bool | 100% | Derived, always consistent with `total_bids` |
| `high_bidder_id` | int | 23% | `null`, never `0`, when nobody has bid |
| `reserve_met` | bool | 100% | |
| `has_reserve` | bool | 30%² | |
| `bid_increment` | number | 100%ᴿ / 98%² | **Not in the search API at all** — from the live bid box |
| `buy_now_price` | number | 3%² | `null`, never `0`, when there is no buy-now price |
| `buyer_premium_pct` | number | **100%ᴿ** | 12%, 12.5%, 15% or 18% on this corpus. Changes what a bid really costs |
| `auto_extension` | bool | 60%² | Whether a late bid extends the close |
| `view_count` / `visitor_count` | int | 100%² | |
| `watcher_count` | int | 68%² | |
| `sale_type` | string | 100% | `Online Auction` (2,027), `Make Offer` (782), `Buy Now` (83) |
| `status` | string | 100% | `active` |
| `audience` | string | 100% | `General Public`, `Approved Bidder`, or the named sale event |
| `start_date` | ISO UTC | 100% | |
| `end_date` | ISO UTC | 100% | Real UTC, offset derived per record |
| `end_date_local` | string | 100% | The site's own US/Eastern wall-clock string |
| `end_date_display` | string | 100% | The site's own display string, verbatim |
| `time_left_seconds` | int | 100% | Never negative |
| `time_remaining` | string | 100% | The site's `d:h:m:s` counter |
| `sale_event_id` | int | 100%¹ | |
| `sale_event_title` | string | 100%¹ | e.g. `Wizpaper - Corrugated Cardboard Factory` |

##### The terms, and the contact that is **not** the seller's

| Field | Type | Fill | What it is |
|---|---|---:|---|
| `inspection_terms` | string | 100%¹ | When and how you may inspect |
| `payment_terms` | string | 100%¹ | |
| `pickup_terms` | string | 100%¹ | Removal window and conditions — the field that decides whether a lot is worth bidding on |
| `special_terms` | string | 100%¹ | |
| `marketplace_contact_name` / `_email` / `_phone` | string | 91.7%ᴿ | **Liquidity Services' own desk — not the seller company's contact.** Measured on 62 lots that publish one: 35 are `LSI-EMEA@liquidityservices.com` or a named Liquidity Services rep, 27 are `buyerhelp@allsurplus.com`, and **0** are on the seller company's own domain. Named `marketplace_contact_*` for exactly that reason |
| `detail_fetched` / `bid_box_fetched` | bool | 100% | Whether each enrichment call actually succeeded on this row, so a `null` is never ambiguous |

ᴿ from the **300-lot live platform run** (300 of 300 rows got both enrichment calls) ·
¹ from the 65-lot detail capture sample · ² from the 63 of 65 captured lots whose bid box answered ·
ᶜ from the full 2,892-lot catalogue walk

#### Fields we do **not** ship, and why

A column of nulls is worse than an honest omission, so these are cut and the measurement is stated:

| Not shipped | Why |
|---|---|
| `meter_reading`, `meter_unit` | Odometer / hour meter: **0 of 65** sampled lots carry one. It is a GovDeals *vehicle* field — the sibling Actor has it |
| `seller_website` | Detail `webAddr`: **0 of 65** |
| `will_ship` | **`false` on 65 of 65** — a column that carries no information |
| `nsn` | A US federal stock number: **0 of 65** (it does not exist on commercial surplus) |
| `sku` | **0 of 65** |
| `final_price`, `is_sold` | There is no sold price to read: the open index held **0 closed lots of 2,892**, and `isSoldAuction` was `false` on all 2,892 |
| `bid_history` | `POST /assets/{asset}/{account}/bids/search` answers **HTTP 200 with `[]`** to an anonymous caller *even on a lot that has bids* — it is login-gated, so the column would be empty on every row |

***

### ⚙️ Input

Leave everything at its defaults for a 50-lot sample of the open commercial catalogue. Nothing is
required.

| Input | Default | What it does |
|---|---|---|
| `startUrls` | — | Paste any allsurplus.com URL — a search page or a lot page. Mixed lists are fine. Wins over `searchType` |
| `searchType` | `all` | `all` · `keyword` · `country` · `state` · `category` · `seller` · `lotUrls` |
| `keywords` | — | One scope per term. `"forklift"` matched 81 lots on 2026-09-18 |
| `countries` | — | Names, ISO codes or a whole region (`"Europe, the Middle East & Africa"`), resolved against the site's own live location menu |
| `states` | — | `TX` → `Texas` automatically; outside North America use the local region name (`Antwerpen`) |
| `categories` | — | Names or codes. A family name expands to every child code — `"Metalworking Equipment"` → 148 codes |
| `sellerAccountIds` | — | Numeric seller ids — everything one company is selling |
| `lotUrls` | — | Individual lot pages |
| `saleType` | Any | Online Auction / Make Offer / Buy Now / Sealed Bid |
| `timeWindow` | Any | Closing today / Newly listed |
| `minBid` / `maxBid` | — | Filtered **before** delivery, so a rejected lot is never charged |
| `includeClosedLots` | `false` | |
| `sortBy` | Closing soonest | Decides what a capped run gets |
| `fetchDetails` | `true` | The two enrichment calls. **No extra charge** |
| `maxLots` | `50` | `0` = the whole catalogue (2,892 lots) |
| `monitorMode` + alerts | `false` | Only new lots since last run; Slack / webhook / email |
| `proxyConfiguration` | Apify proxy | Datacenter is enough — measured 29/29 and 65/65 clean |
| `requestConcurrency` | `5` | 1–10 |
| `diagnose` | `false` | Dumps the raw API response and the normalised row, then stops |

#### Which filters are **server-side** (and which the API silently ignores)

This API **accepts a filter it does not apply** and answers HTTP 200 with the whole corpus. Every
one was tested individually on 2026-09-18 and only the working ones are ever sent:

| Filter | Server-side? | Evidence |
|---|---|---|
| `businessId=GI` | ✅ | 2,892 of 29,366 |
| country / region | ✅ | USA 1,150 · Germany 490 · EMEA 1,581 |
| state / province | ✅ | Texas 66 · Antwerpen 2 |
| sale type | ✅ | Online Auction 2,027 |
| category | ✅ | code 454 → 33 |
| keyword | ✅ | forklift → 81 |
| seller account | ✅ | 27040 → 13 |
| closing today | ✅ | 14 |
| `city` facet | ❌ ignored | still returns all 2,892 — never sent |
| `sellerDisplayName`, `condition`, `currencyTypeCode` facets | ❌ ignored | still return all 2,892 — never sent |
| two countries in one OR list | ❌ HTTP 400 | so N countries = N scopes |

***

### 💵 Pricing

| Event | Rate |
|---|---|
| **Per commercial surplus lot returned** (`lot-scraped`) | **$0.0045** — **$4.50 per 1,000 lots** |
| Run start | **none.** There is no start fee |
| Lot details, specs, photos, terms, buyer's premium | **included** — no separate event |

- The **whole open commercial catalogue** (2,892 lots, fully enriched) costs about **$13.01**.
- A **50-lot** default run costs **$0.23**.
- You are charged **only for rows delivered to your dataset.** A lot rejected by your bid filters,
  a lot AllSurplus has no record for, and any row outside the commercial corpus are all reported in
  `RUN_STATS` and **never billed**. Delivery and billing are atomic, so if you hit a run cost cap
  you keep exactly the rows you paid for — never billed-but-missing.
- A run that matches nothing costs nothing.

**Measured throughput** (live platform runs, 2026-09-18, 512 MB, default concurrency 5, fully
enriched): 25 lots in **17 s**, 300 lots in **151 s** — 605 HTTP calls with **0 retries**. Your
Apify platform compute for the 300-lot run was **$0.0066**, i.e. the platform cost is a rounding
error next to the per-row price; you are paying for the data, not for our container.

***

### ❓ FAQ

**Does this return GovDeals / government surplus lots?**
No, by design. `businessId=GI` is hard-coded, sent as a server-side filter, re-checked on every row
on the way out, and published on each row as `business_id`. Government surplus is covered by our
sibling Actor `scrapersdelight/govdeals-auction-scraper` — the two complement each other and neither
resells the other's rows.

**Why would I want the commercial half specifically?**
Because that is the half with a named company and a real plant address on it. GovDeals rows name a
public agency; GoDove rows name Shell, Schneider Electric, HP, Seagate, ArcelorMittal, Bristol Myers
Squibb, Tyson. If your buyer cares about industrial assets, corporate disposals, or the company
behind the lot, this is the corpus.

**How does this compare with the other AllSurplus scraper on the store?**
There is exactly one — and this one is **more featured at a lower price**. That one walks the
**aggregate** AllSurplus corpus, so most of what it returns is GovDeals government surplus resold
under an AllSurplus name, and every field it ships comes from the search row alone: it calls neither
the asset-detail endpoint nor the live bid box, so it has no condition, quantity, spec attributes,
seller spec PDFs, photo gallery, sale event, category path, inspection/payment/removal terms, bid
increment or buyer's premium. It also charges a run-start fee on top of its per-row rate. This Actor
is commercial-only (0 GovDeals rows, asserted), ships all of those fields, and has no start fee.

**How big is it, really?**
Small and honest: **2,892 open lots** across **131 seller accounts** on 2026-09-18, walked end to
end. The whole catalogue came back in 29 pages with **0 duplicates** and the walked count equalled
the API's own `x-total-count` exactly. It is not a million-row dataset and this page will not
pretend it is.

**Is the seller contact email the company's?**
**No** — and it is named `marketplace_contact_*` so nobody is misled. All 62 sampled contacts are
Liquidity Services addresses (`LSI-EMEA@liquidityservices.com`, `buyerhelp@allsurplus.com`) or a
named Liquidity Services rep. The lead value here is `seller_company` + `pickup_address`, which you
enrich yourself.

**Can I get the bid history?**
No. The endpoint exists but is login-gated: it answers HTTP 200 with `[]` to an anonymous caller
even on a lot that has bids. Rather than ship an always-empty column, it is cut and documented.

**Can I get sold prices / results?**
No. The open index held **0 already closed lots of 2,892**, and `isSoldAuction` was `false` on every
one, so there is nothing to read a hammer price from. `includeClosedLots` exists so that a closed lot
can never be delivered and billed as if it were still bidable.

**Do I need a login, a cookie or a CAPTCHA solver?**
No. The Actor uses the site's own public JSON API with the anonymous web key the site publishes in
its own JavaScript bundle, and it **re-reads that key from the live bundle on every run** so a key
rotation heals itself. No browser, no session pinning, no residential proxy.

**Is the shared Apify datacenter proxy enough?**
Yes. Measured on 2026-09-18: a live 300-lot platform run made **605 HTTP calls with 0 retries**,
300 of 300 asset-detail calls and 300 of 300 live bid boxes answered, and a full catalogue walk took
29 of 29 search pages clean. You can point `proxyConfiguration` anywhere you like.

**How do amounts work across countries?**
Lots are priced in **six currencies** (USD, EUR, ZAR, CNY, AUD, GBP). `currency` is per lot and
nothing is converted. `minBid`/`maxBid` compare the raw number in the lot's own currency — set them
per-currency scope if that matters to you.

**Why are `latitude`/`longitude` missing on some rows?**
Because the source does not publish them for those lots (53.3% coverage). They come back `null`, and
never as a fabricated `0,0` that would drop a lot in the Gulf of Guinea.

**Can I watch one company?**
Yes — take its `seller_account_id` from any row, put it in `sellerAccountIds`, and turn on
`monitorMode` to get only what is newly listed, with a Slack/webhook/email alert.

**How do I run the whole catalogue?**
Set `maxLots: 0`. Expect ~2,892 rows, a few minutes, and about $13.01.

**Do the different `searchType` modes all actually work?**
Each one was run on the platform, not just unit-tested: `all` (300 rows), `keyword` (10), `country`
(10), a whole `region` (10), `category` with a family name expanded to 148 codes (10), `lotUrls`
(1), a pasted lot page via `startUrls` (1), and `monitorMode` twice — the second pass returned
**0 new**, which is the proof that the monitor's memory really persists between runs. Every one
delivered exactly what it billed.

**Something looks wrong — how do I show you?**
Turn on `diagnose`. It dumps the raw API response, the `businessId` values on the page, the
page-1/page-2 overlap and the normalised row into the key-value store, then stops. It costs nothing.

***

### 🧪 How this Actor is verified

- **31 fixture files, 1.8 MB of real captured bytes** from the live API through the Apify
  datacenter proxy, byte counts pinned in `fixtures/MANIFEST.json`.
- **An offline validator** (`node offline_validate.mjs`) that re-derives every number on this page
  from those bytes rather than asserting a stored one — including an explicit check that **zero
  GovDeals rows** survive the pipeline when it is fed a real aggregate page containing 85 of them.
- Truncation guard: a search page is 190–255 KB, exactly the window where the datacenter proxy cuts
  a body mid-stream under HTTP 200, so every response is checked against its declared
  `Content-Length` before it is parsed.
- A non-200 is treated as a transport failure and retried on a **fresh proxy session**, never as
  end-of-data.

### ⚖️ Source, legal and fair use

- AllSurplus lot listings are **public**: no account, no login and no cookie is needed to read them,
  and Liquidity Services publishes the seller company and the pickup address *by convention*, because
  buyers must be able to inspect the equipment and remove it after the sale. That convention is the
  commercial basis for this data. It is **not** the same basis as government surplus, where public
  sale is required by state procurement and disposal statutes — that corpus is out of scope here and
  is covered by the sibling Actor. The two are not blurred together.
- `https://www.allsurplus.com/robots.txt` on 2026-09-18 contains `Crawl-delay: 5`,
  `Disallow: /account/` and `Disallow: /assets/` (the static asset directory, with
  `Allow: /assets/images/`). The lot pages this Actor references live at `/asset/<id>/<account>` and
  are **not** under either Disallow — the same file publishes
  `Sitemap: https://files.lqdt1.com/zstcntr-sitemap/Assets_Sitemap_AllSurplus.xml`, i.e. the site
  asks search engines to crawl exactly these lot pages.
- This Actor reads the site's **own public JSON API** with the **anonymous** web key the site ships
  in its public JavaScript bundle. No credential was extracted, guessed, or minted, and no
  authenticated endpoint is touched.
- No personal data is collected. `seller_company` is a business name; `pickup_address` is a business
  premises; `marketplace_contact_*` is a corporate helpdesk. There are no buyer identities in the
  output.
- Bid amounts and time-remaining are snapshots at scrape time. Re-run for fresh values.
- This is an independent tool and is **not affiliated with or endorsed by** AllSurplus, GoDove,
  GovDeals or Liquidity Services. You are responsible for complying with their Terms of Service.

# Actor input Schema

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

Paste any allsurplus.com URL straight out of your address bar — a search/browse page or a single lot page (https://www.allsurplus.com/asset/1/27040). Search pages and lot pages can be mixed in one run. While this list is non-empty it WINS over the 'What to scrape' picker below. Government-surplus lots pasted here are rejected and never charged.

## `searchType` (type: `string`):

Which slice of the commercial catalogue to walk. 'Everything' walks all open commercial lots. The others each need their own list below; if the list is empty the run falls back to a sample of the open catalogue rather than failing.

## `keywords` (type: `array`):

Search terms, one scope each (e.g. "forklift", "CNC", "chiller", "wafer"). Used when 'What to scrape' is 'By keyword'. Measured 2026-09-18: "forklift" matched 81 open commercial lots.

## `countries` (type: `array`):

Country names, ISO codes or a whole region, one scope each — e.g. "United States of America", "USA", "Germany", "DEU", or the region "Europe, the Middle East & Africa". Resolved against AllSurplus' own live location menu. Used when 'What to scrape' is 'By country or region'. Measured 2026-09-18: USA 1,150 lots, Germany 490, Israel 405, South Africa 331.

## `states` (type: `array`):

US and Canadian two-letter codes are expanded automatically (TX -> Texas); outside North America type the local region name exactly as AllSurplus shows it ("Antwerpen", "Hauts-de-France"). One scope each. Used when 'What to scrape' is 'By state'.

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

Category names or AllSurplus category codes. A family name such as "Metalworking Equipment" or "Heavy Equipment and Construction" is expanded to every child code automatically, because the API matches the exact code on a lot and would otherwise return nothing. Used when 'What to scrape' is 'By category'.

## `sellerAccountIds` (type: `array`):

Numeric AllSurplus seller account ids — the second number in a lot URL (https://www.allsurplus.com/asset/1/27040 -> 27040). Every lot from those sellers. Used when 'What to scrape' is 'By seller account id'.

## `lotUrls` (type: `array`):

Individual AllSurplus lot pages to resolve directly, e.g. https://www.allsurplus.com/asset/1/27040. Used when 'What to scrape' is 'Only the lot URLs I list'. A lot AllSurplus no longer has a record for is reported, not billed.

## `saleType` (type: `string`):

Restrict to one sale format, applied server-side. Measured 2026-09-18 across the 2,892 open commercial lots: Online Auction 2,027, Make Offer 782, Buy Now 83.

## `timeWindow` (type: `string`):

Restrict to lots closing today or to newly listed lots, applied server-side. 'Any' is the whole open catalogue.

## `minBid` (type: `integer`):

Drop lots whose current bid is below this. Applied before the lot is enriched or delivered, so a rejected lot is never charged. A lot with no bid yet is never dropped by this filter. Note lots are priced in six currencies (USD, EUR, ZAR, CNY, AUD, GBP) and this compares the raw number, not a converted one.

## `maxBid` (type: `integer`):

Drop lots whose current bid is above this. Applied before the lot is enriched or delivered, so a rejected lot is never charged. Compares the raw number in the lot's own currency.

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

Off by default. Measured 2026-09-18: 0 of 2,892 lots in the open index had already closed, so this normally changes nothing — it exists so a closed lot can never be delivered and billed as if it were bidable.

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

Which lots come first, which decides what a capped run gets. Applied server-side.

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

On by default. Adds two calls per lot — the asset record and the live bid box — which fill condition, quantity, VIN/serial, spec attributes, seller spec PDFs, the full photo gallery, inspection/payment/removal terms, bid increment, buyer's premium and view counts. Measured fill 2026-09-18 on 65 lots: detail 65/65, bid box 63/65. There is no extra charge for it; turn it off to run faster on a listings-only pull.

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

Stop after this many lots across every scope, so a run's cost is predictable. 0 means no limit — the whole open commercial catalogue was 2,892 lots on 2026-09-18. Lots rejected by your filters do not count against it and are never charged.

## `monitorMode` (type: `boolean`):

Remembers the lots each scope has already returned in a named key-value store that survives between scheduled runs, and delivers only the newly listed ones. The first run returns everything and seeds the memory.

## `alertOnNewLot` (type: `boolean`):

Send an alert for each lot that is new since the previous monitoring run. Only fires while Monitor mode is on.

## `alertOnBidChange` (type: `boolean`):

Send an alert when the current bid changes on one of the lot ids listed below. Only fires while Monitor mode is on.

## `watchlistLotIds` (type: `array`):

Lot ids to watch for bid movement, in the dataset's own "assetId-accountId" form, e.g. "1-27040".

## `webhookUrl` (type: `string`):

Each alert is POSTed to this URL as JSON.

## `slackWebhookUrl` (type: `string`):

Each alert is posted to this Slack incoming webhook as a card with the lot title, seller, bid and photo.

## `emailRecipients` (type: `array`):

Email addresses to send a digest of the alerts to, via Apify's own send-mail actor.

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

Apify's shared datacenter proxy is enough for this source — measured 29/29 search pages and 65/65 detail calls clean on 2026-09-18, with no residential bandwidth, no session pinning and no browser.

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

How many AllSurplus API calls run at once. 5 is the measured-good setting; raising it speeds up a large run but is more likely to draw rate limiting.

## `diagnose` (type: `boolean`):

Fetch a few rows, dump the raw API response, the normalised row, the businessId values on the page and the page-1/page-2 overlap to the key-value store, then stop. Use this before reporting a problem — it costs nothing and shows exactly what AllSurplus returned.

## Actor input object example

```json
{
  "startUrls": [],
  "searchType": "all",
  "keywords": [
    "forklift"
  ],
  "countries": [
    "United States of America"
  ],
  "states": [
    "TX"
  ],
  "categories": [
    "Metalworking Equipment"
  ],
  "sellerAccountIds": [
    "27040"
  ],
  "lotUrls": [],
  "saleType": "any",
  "timeWindow": "any",
  "includeClosedLots": false,
  "sortBy": "closing",
  "fetchDetails": true,
  "maxLots": 25,
  "monitorMode": false,
  "alertOnNewLot": true,
  "alertOnBidChange": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestConcurrency": 5,
  "diagnose": false
}
```

# Actor output Schema

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

One row per live AllSurplus COMMERCIAL lot (businessId GI): named corporate seller, pickup street address, city/state/country, closing time, current bid, bid count, bid increment, buyer's premium, sale type, category, condition, quantity, VIN/serial, spec attributes, seller spec PDFs, photo gallery, sale event and the inspection/payment/removal terms. Government-surplus lots are excluded.

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

Rows seen, unique lots, in-run duplicate rate, out-of-scope rows rejected, detail and bid-box fill, per-field fill percentages and HTTP call counts for this run.

# 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": [],
    "searchType": "all",
    "keywords": [
        "forklift"
    ],
    "countries": [
        "United States of America"
    ],
    "states": [
        "TX"
    ],
    "categories": [
        "Metalworking Equipment"
    ],
    "sellerAccountIds": [
        "27040"
    ],
    "lotUrls": [],
    "sortBy": "closing",
    "fetchDetails": true,
    "maxLots": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/allsurplus-industrial-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 = {
    "startUrls": [],
    "searchType": "all",
    "keywords": ["forklift"],
    "countries": ["United States of America"],
    "states": ["TX"],
    "categories": ["Metalworking Equipment"],
    "sellerAccountIds": ["27040"],
    "lotUrls": [],
    "sortBy": "closing",
    "fetchDetails": True,
    "maxLots": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/allsurplus-industrial-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 '{
  "startUrls": [],
  "searchType": "all",
  "keywords": [
    "forklift"
  ],
  "countries": [
    "United States of America"
  ],
  "states": [
    "TX"
  ],
  "categories": [
    "Metalworking Equipment"
  ],
  "sellerAccountIds": [
    "27040"
  ],
  "lotUrls": [],
  "sortBy": "closing",
  "fetchDetails": true,
  "maxLots": 25
}' |
apify call scrapersdelight/allsurplus-industrial-lot-scraper --silent --output-dataset

```

## MCP server setup

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