# GovDeals Scraper — Government Surplus Auction Lots (`scrapersdelight/govdeals-auction-scraper`) Actor

Government-surplus auction lots from GovDeals: title, seller agency, category, current bid, bid count, bid increment, buyer's premium, buy-now price, closing time, quantity, condition, VIN, meter, city/state and photos. Paste a GovDeals URL, or filter by state, category, keyword or seller.

- **URL**: https://apify.com/scrapersdelight/govdeals-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/actors/running/actors-in-store.md#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

## 🏛️ GovDeals Scraper — Government Surplus Auction Lots

Turn [GovDeals](https://www.govdeals.com) — the largest US marketplace for government and
municipal surplus — into a structured lot feed. **One row per lot**, carrying `title`,
`agency`, `category`, `current_bid`, `opening_bid`, `bid_increment`, **`buyer_premium_pct`**,
`buy_now_price`, `total_bids`, `end_date`, `quantity`, `condition`, `make`, `model`,
`vin_serial`, `meter_reading`, `city`/`state`, `latitude`/`longitude`, seller contact and the
full photo gallery — 67 fields in all.

Paste a govdeals.com URL, or filter by state, category, keyword, seller, sale type, seller type
and bid range. **No login. No API key. No cookies. No CAPTCHA solving. No browser.**

**26,193 open lots** in the whole live catalogue, counted through the API on 2026-08-12 — not
quoted from a marketing page. Every run logs the live count for the scope you asked for.

***

### ⚡ Quick start

Click **Try for free** and hit **Start**. This is the input the Actor ships with, and it returns
rows with zero edits:

```json
{
  "searchType": "all",
  "fetchDetails": true,
  "maxLots": 50
}
```

Measured on that exact input, 2026-08-12: **50 lots in 38 seconds, 103 HTTP calls, 50/50 with a
detail record and 50/50 with a live bid box.**

Or skip the picker entirely and paste what is in your address bar:

```json
{
  "startUrls": [
    "https://www.govdeals.com/search?kWord=forklift&stateName=Texas",
    "https://www.govdeals.com/asset/13273/21430"
  ],
  "maxLots": 50
}
```

Search pages and single lot pages can be mixed in the same list. Go to GovDeals, apply your
filters, copy the URL, paste it here.

***

### 🎯 The wedge: the three fields that turn a headline bid into a decision

Most surplus scrapers hand you a title and a current bid. The bid is not the price and the
listing is not the opportunity:

- **`buyer_premium_pct`** — the seller's buyer premium, straight from GovDeals' own bid box
  (commonly 9.5–15%; the two sample rows below carry 12.5% and 10%). It is the difference
  between an $81,000 hammer and a $91,125 landed cost. The direct competitor's own published
  field list runs alphabetically from `accountId` to `make` and contains **no buyer premium, no
  bid increment and no buy-now price**.
- **`view_count` / `visitor_count` / `watcher_count`** — GovDeals' own demand signals: how many
  times a lot has been opened, by how many distinct visitors, and how many people are watching
  it. A high watcher count on a lot closing soon means you are about to be outbid. A
  zero-watcher lot closing today is where the arbitrage is.
- **Category child-code expansion** — a GovDeals lot only carries its own **leaf** category
  code, so filtering on a family code silently misses almost everything: `206` ("Clothing,
  Jewelry, and Accessories") returns **85** lots, while its child `56` ("Jewelry") returns
  **1,096**. This Actor resolves `"Material Handling Equipment"` + `36` into **39 codes** and
  queries all of them at once. Pasting `?category=36` resolved to **26 codes** on 2026-08-12.

***

### 📕 Read this before you buy rows

1. **GovDeals is a mixed catalogue.** Jewelry, office furniture and IT gear sit next to dump
   trucks. `make` / `model` / `vin_serial` / `meter_reading` are populated on **vehicles and
   plant only**. Their fill is a function of what you asked for, not a defect — see the two
   tables below.
2. **A capped run is not a random sample.** `sortBy` decides *which* lots you get when `maxLots`
   is small relative to the scope. The default is closing-soonest, which is right for sniping
   and wrong for market research — use `newest` or `bidHigh` for that.
3. **Closed lots are dropped before you are billed.** GovDeals leaves just-closed lots in its
   open search index for a while. With the default sort that would otherwise hand you a page of
   dead lots. Set `includeClosedLots: true` only if you are building sold comps, and expect null
   quantity/condition/gallery on those rows.
4. **`current_bid` is a snapshot.** This is a live auction. Rows are true at `scraped_at`, and a
   lot closing in minutes will move between your run and your read.
5. **There is no radius search.** GovDeals' proximity parameters return nothing for anonymous
   callers, so this Actor does not pretend to support them. Use `states`, or filter
   `latitude`/`longitude` yourself.

***

### 📦 What you get — one row per lot

| Field group | Fields |
|---|---|
| **Identity** | `lot_id`, `asset_id`, `account_id`, `auction_id`, `inventory_id`, `lot_number`, `lot_url` |
| **The item** | `title`, `description`, `category`, `category_code`, `parent_category`, `quantity`, `quantity_unit`, `condition`, `condition_code` |
| **Money** | `current_bid`, `opening_bid`, `bid_increment`, `buy_now_price`, **`buyer_premium_pct`**, `currency` |
| **Competition** | `total_bids`, `has_bid`, `high_bidder_id`, `has_reserve`, `reserve_met`, `auto_extension`, **`view_count`**, **`visitor_count`**, **`watcher_count`** |
| **Vehicles / plant** | `make`, `model`, `model_year`, `vin_serial`, `meter_reading`, `meter_unit`, `meter_accurate` |
| **Seller** | `agency`, `agency_id`, `agency_url`, `seller_type`, `seller_contact_name`, `seller_contact_email`, `seller_contact_phone`, `audience` |
| **Location** | `address`, `city`, `state`, `zipcode`, `country`, `latitude`, `longitude` |
| **Timing** | `start_date`, `end_date`, `time_left_seconds`, `sale_type`, `status` |
| **Terms** | `inspection_terms`, `payment_terms`, `pickup_terms`, `special_terms` |
| **Media / run** | `images` (array), `thumbnail`, `image_count`, `detail_fetched`, `scraped_at` |

All dates are **ISO-8601 UTC**. All money fields are numbers, never strings. GovDeals publishes
its clocks in US/Eastern wall time; this Actor converts using the offset each record itself
reveals, so you never have to guess EST vs EDT.

Every run also writes a **`RUN_STATS`** record to the key-value store — rows seen, unique lots,
measured duplicate rate, detail and bid-box fill, closed lots skipped and HTTP call counts — so
you can audit any claim on this page against your own run.

***

### 📊 Field fill — measured on 200 lots

From a live proxy run of **200 lots** (`searchType: "all"`, default `sortBy: "closing"`,
`fetchDetails: true`) — 30 states, 51 selling agencies, 8.6 photos per lot on average, and
**200/200 lots enriched**:

| 100% filled | Partly filled | Depends on the item |
|---|---|---|
| `lot_id`, `asset_id`, `account_id`, `auction_id`, `lot_url`, `title`, `description`, `category`, `category_code`, `sale_type`, `status`, `currency`, `current_bid`, `opening_bid`, `bid_increment`, `buyer_premium_pct`, `total_bids`, `has_bid`, `has_reserve`, `reserve_met`, `auto_extension`, `view_count`, `visitor_count`, `quantity`, `quantity_unit`, `condition`, `condition_code`, `agency`, `agency_id`, `seller_type`, `audience`, `address`, `city`, `state`, `zipcode`, `country`, `start_date`, `end_date`, `time_left_seconds`, `inspection_terms`, `payment_terms`, `pickup_terms`, `special_terms`, `images`, `thumbnail`, `image_count` | `latitude`/`longitude` 98.5%, `watcher_count` 67.5%, `parent_category` 63%, `high_bidder_id` 53.5% (null until somebody bids), `make` 45.5%, `agency_url` 43.5% | `model` 37.5%, `inventory_id` 35.5%, `vin_serial` 15.5%, `seller_contact_name`/`email`/`phone` 13.5%, `model_year` 13%, `meter_unit` 12%, `meter_accurate` 11%, `meter_reading` 10.5%, `buy_now_price` 6%, `lot_number` <1% |

#### The same fields on a vehicle-scoped run

**The headline that could mislead you: on an unfiltered run this is not a VIN product.** Scope
it and it becomes one. On `searchType: "keyword"`, `"dump truck"`, 30 lots, the same fields
measured **make 90%, model 90%, VIN 83%, model\_year 80%, meter\_reading 60%**. A 25-lot
`dump truck` run on 2026-08-12 returned **18 of 25 rows carrying make + VIN + meter together**.

- `seller_contact_email` (13.5% overall) is the agency's published surplus contact where the
  seller chose to publish one — a real, useful lead field, but do not plan on every row having it.
  The switch is GovDeals' own per-lot `hidePocinfo` flag: re-measured 2026-08-19 on 25 raw detail
  records, **24 of 25 had `hidePocinfo: true` and exactly the 1 lot with it `false` carried name +
  email + phone**. Scope where sellers publish and it inverts: a 30-lot `saleType: "5"` (Make
  Offer) run returned **30/30 with a seller contact name**.
- `buy_now_price` is only set on Buy-Now / strike-price lots (2–6% on the default closing-soonest
  mix, 20.5% on another 200-lot slice). Scope to it and it is complete: a 30-lot
  `saleType: "4"` run measured **30/30**. It tracks the sale formats in your scope, nothing else.
- `lot_number` is GovDeals' sale-event lot number, and it is a **sale-format** field, not a rare
  one. Re-measured 2026-08-19 straight off GovDeals' own search API, 50 lots per format:
  **Online Auction 0/50 · Buy Now 0/50 · Make Offer 47/50** — and a 30-lot `saleType: "5"` run
  through this Actor returned **27/30**. Because timed online auctions are the bulk of the
  catalogue, an unfiltered run shows ~0%. GovDeals also writes `-1` on some records when there is
  none; this Actor returns `null` for both the sentinel and a missing value, never `"-1"`.

Every run prints its own measured fill in the log and in `RUN_STATS`.

#### Listing-only mode (`fetchDetails: false`)

Measured over **1,000 lots on 10 contiguous pages** (1,000 unique, 0 duplicates): you keep `title`,
`description`, `category`, `current_bid`, `opening_bid`, `total_bids`, `sale_type`, `agency`,
`city`/`state`/`zip`, `start_date`/`end_date`, one photo and the lot URL at **100%** fill, in
**29 seconds** and **12 HTTP calls**. What you give up: quantity, condition, VIN, meter,
seller contact, terms, the full gallery, and every bid-box field
(`bid_increment`, `buy_now_price`, `buyer_premium_pct`, view/visitor/watcher counts).
**The price per lot is identical either way** — this buys speed, not money.

***

### 🧭 How to run it

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

```json
{ "startUrls": ["https://www.govdeals.com/search?kWord=forklift&stateName=Texas"], "maxLots": 50 }
```

Read out of the URL: **keyword** (`kWord`), **category** (`category`, expanded to every child
code), **state** (`stateName` or `state`), **seller** (`accountId`) and **sale type**
(`auctionTypeId`, `sellerType`). Single lot pages (`/asset/<assetId>/<accountId>`) are resolved
straight off the asset endpoint. Lot URLs and search URLs can be mixed in one list, and
`startUrls` overrides the `searchType` picker whenever it is not empty.

Deliberately **not** read from the URL: its own price (`sPrice`/`ePrice`) and sort (`sf`/`so`)
parameters. Those have run-level inputs — `minBid`/`maxBid` and `sortBy` — that apply to every
scope in the run, and honouring one pasted URL's sort while ignoring another's would be worse
than not supporting it.

#### Or pick a source — six of them, one row shape

| `searchType` | What it does |
|---|---|
| `all` | Walks the whole live GovDeals catalogue (26,193 open lots on 2026-08-12), closing-soonest first |
| `state` | One **server-side** query per state — `"TX"`, `"Florida"`, `"ON"` all work |
| `category` | Give a code (`36`) **or a name** (`"Heavy Equipment and Construction"`); resolved against GovDeals' live category menu and expanded to every child code |
| `keyword` | GovDeals' own site search, one walk per term |
| `seller` | One or more GovDeals account ids (the second number in a lot URL); all ids ride in one query |
| `lotUrls` | Paste specific lots — resolved straight off the asset endpoint, live bid included. Ideal as a watchlist |

```json
{ "searchType": "state", "states": ["TX", "OK"], "maxLots": 500, "sortBy": "newest" }
```

```json
{ "searchType": "category", "categories": ["Heavy Equipment and Construction"],
  "sellerType": "1", "minBid": 5000, "maxLots": 0 }
```

Stackable server-side filters: `sellerType` (government vs commercial reseller), `saleType`
(online auction / buy now / make offer / sealed bid), `timeWindow` (closing today / new
listings) and `sortBy`. `minBid` / `maxBid` are applied client-side to keep the dataset clean —
rows they remove are never charged to you.

#### Monitor mode — the recurring play

```json
{ "searchType": "keyword", "keywords": ["excavator"], "monitorMode": true,
  "slackWebhookUrl": "https://hooks.slack.com/services/…", "maxLots": 0 }
```

Set `monitorMode: true` and the Actor remembers, in a **named** key-value store, every lot id it
has already returned *for that exact scope* — so a scheduled run outputs, and bills, only
genuinely new listings. Closed lots age out of the memory a day after they close. Verified:
run 1 → `12 new of 12`, run 2 → `0 new of 12`, and a different keyword gets its own independent
seen-set. Pair it with an Apify **Schedule** (hourly for bids, daily for new inventory).

Optional `webhookUrl`, `slackWebhookUrl` (posts a card with the photo, bid, location and closing
time) and `emailRecipients` fire on each new lot; `alertOnBidChange` + `watchlistLotIds` alert
when a bid moves on lots you are tracking.

***

### 🧾 Sample rows

Two real rows from runs on 2026-08-12. Long strings are cut at `…` and a few fields are left out
for length — no value has been altered. They are deliberately different shapes so the conditional
fill above is visible rather than described.

<details open>
<summary><b>A vehicle lot</b> — make / model / VIN / meter populated</summary>

```jsonc
{
  "lot_id": "3-32018",
  "asset_id": 3,
  "account_id": 32018,
  "auction_id": 2,
  "lot_url": "https://www.govdeals.com/asset/3/32018",

  "title": "Mack 2019 Granite Dump truck Allison transmission",
  "description": "2019 Mack GR64F Tri-Axle Dump Truck Powered by a 455 HP Mack MP8 diesel engine and Allison 6-speed automatic transmissio…",
  "category": "Dump Trucks",
  "category_code": "645",
  "parent_category": "Trucks",

  "sale_type": "Online Auction",
  "status": "active",
  "currency": "USD",
  "current_bid": 81000,
  "opening_bid": 29999,
  "bid_increment": 1000,
  "buy_now_price": null,
  "buyer_premium_pct": 12.5,          // 81,000 hammer -> 91,125 landed
  "total_bids": 7,
  "has_bid": true,
  "high_bidder_id": 3363501,
  "has_reserve": true,
  "reserve_met": false,
  "auto_extension": true,
  "view_count": 172,
  "visitor_count": 68,
  "watcher_count": 5,

  "quantity": 1,
  "quantity_unit": "Each",
  "condition": "Used/See Description",
  "condition_code": "SD",

  "make": "Mack",
  "model": "Granite GR64F",
  "model_year": "2019",
  "vin_serial": "1M2GR4GC9KM009329",
  "meter_reading": 222941,
  "meter_unit": "Miles",
  "meter_accurate": "Yes",

  "agency": "Seller 32018 - ASATK",
  "agency_id": 32018,
  "seller_type": "Commercial Seller",
  "seller_contact_name": null,        // this seller publishes no contact
  "seller_contact_email": null,
  "seller_contact_phone": null,

  "address": "1165 Vineyard Ct",
  "city": "Stone Mountain",
  "state": "GA",
  "zipcode": "30083-2547",
  "country": "USA",
  "latitude": 33.813733,
  "longitude": -84.203756,

  "start_date": "2026-08-10T22:04:31.000Z",
  "end_date": "2026-08-17T21:49:31.000Z",
  "time_left_seconds": 417667,

  "inspection_terms": "At the seller's discretion, inspection may be permitted. Inspection is…",
  "payment_terms": "Wire Transfer is the only payment option for this item. The Wire Trans…",
  "pickup_terms": "***SHIPPING IS NOT AVAILABLE***\n\nThe buyer is responsible for all aspe…",

  "image_count": 34,
  "thumbnail": "https://webassets.lqdt1.com/assets/photos/32018/32018_3_76825281-….jpeg",
  "detail_fetched": true,
  "scraped_at": "2026-08-13T01:48:23.784Z"
}
```

</details>

<details>
<summary><b>A non-vehicle lot</b> — no VIN, but a named seller contact and a buy-now price</summary>

```jsonc
{
  "lot_id": "1265-9260",
  "lot_url": "https://www.govdeals.com/asset/1265/9260",

  "title": "Amano Cash & Credit Card Payment Machines",
  "description": "Amano Cash & Credit Card Payment Machine, Quantity: 3, Selling as 1 lot.\nASSETS 2006001, 2006002, 20…",
  "category": "Vending Equipment",
  "category_code": "VE",
  "parent_category": null,

  "sale_type": "Online Auction",
  "current_bid": 100,
  "opening_bid": 100,
  "bid_increment": 10,
  "buy_now_price": 105,               // set only on Buy-Now / strike-price lots
  "buyer_premium_pct": 10,
  "total_bids": 0,
  "has_bid": false,
  "high_bidder_id": null,             // null = nobody has bid, not 0
  "has_reserve": false,
  "reserve_met": true,
  "view_count": 94,
  "visitor_count": 47,
  "watcher_count": 2,

  "quantity": 1,
  "quantity_unit": "Lot",             // "Lot", not "Each" — 3 machines in one row
  "condition": "Used/See Description",

  "make": null,                       // not a vehicle: the whole block is null
  "model": null,
  "vin_serial": null,
  "meter_reading": null,

  "inventory_id": "P&S 881",
  "agency": "Alameda County, CA",
  "agency_url": "http://www.acgov.org/",
  "seller_type": "Government Seller",
  "seller_contact_name": "Dzemail Terovic",
  "seller_contact_email": "Dzemail.terovic@acgov.org",
  "seller_contact_phone": "+15103266241",
  "audience": "General Public",

  "city": "Oakland",
  "state": "CA",
  "zipcode": "94607-3944",
  "latitude": 37.800716,
  "longitude": -122.277412,

  "end_date": "2026-08-13T02:00:02.000Z",
  "time_left_seconds": 627,
  "image_count": 12,
  "detail_fetched": true,
  "scraped_at": "2026-08-13T01:49:35.119Z"
}
```

</details>

**Fields people misread:**

- `high_bidder_id: null` means nobody has bid; it is never `0`. `total_bids: 0` with
  `has_bid: false` says the same thing from the other side.
- `buyer_premium_pct` is a **percentage** (`12.5` = 12.5%), not a multiplier and not a 0–1 fraction.
- `quantity: 1` with `quantity_unit: "Lot"` can mean three machines. Read the unit, not the number.
- `reserve_met` is inverted from GovDeals' own `isReserveNotMet` flag so that `true` always means
  the good thing. `null` means GovDeals published no reserve state.
- `time_left_seconds` is computed at `scraped_at` and does not keep counting. Schedule on
  `end_date`, which is absolute UTC.

***

### ⚙️ Input

| Field | Type | Default | What it does |
|---|---|---|---|
| **1. What to scrape** | | | |
| `startUrls` | string\[] | `[]` | Paste any govdeals.com search or lot URL. **Overrides `searchType` when non-empty.** Reads keyword, category, state, seller and sale type out of the URL |
| `searchType` | select | `all` | Used only when `startUrls` is empty: `all` · `state` · `category` · `keyword` · `seller` · `lotUrls` |
| `states` | string\[] | `["TX"]` | `"TX"`, `"California"`, `"ON"`. One server-side query **per state**, so each entry costs a request per page |
| `categories` | string\[] | `["Heavy Equipment and Construction"]` | Codes or names; expanded to every child code. Unmatched names are logged and skipped |
| `keywords` | string\[] | `["forklift"]` | One walk per term — two terms take roughly twice as long |
| `sellerAccountIds` | string\[] | `[]` | Numeric account ids; all ride in **one** query, so ten sellers cost the same as one |
| `lotUrls` | string\[] | `[]` | Full lot URLs. One request per lot, no search walk — the cheapest way to re-price a watchlist |
| **2. Filters** | | | |
| `sellerType` | select | `any` | Government vs commercial reseller. **Server-side**, so it costs nothing |
| `saleType` | select | `any` | Online Auction · Buy Now · Make Offer · Sealed Bid. **Server-side** |
| `timeWindow` | select | `any` | Closing today · New listings. **Server-side** |
| `minBid` | integer | — | Client-side. Rows it removes are never charged. Lots with no bid yet are always kept |
| `maxBid` | integer | — | Client-side. Rows it removes are never charged |
| `includeClosedLots` | boolean | `false` | ON builds sold comps; expect null quantity/condition/gallery on closed rows |
| `sortBy` | select | `closing` | Decides *which* lots a capped run gets. `closing` · `newest` · `bidHigh` · `bidLow` · `bestMatch` |
| **3. Depth & cost** | | | |
| `fetchDetails` | boolean | `true` | Two extra requests per lot for VIN, meter, terms, gallery and the live bid box. **Same price either way** |
| `maxLots` | integer | `50` | **Your hard cost cap.** `0` = no cap (26,193 lots ≈ $79). Duplicates and filtered rows never count against it |
| **4. Monitoring & alerts** | | | |
| `monitorMode` | boolean | `false` | Only lots not seen before **for this exact scope**. The one mode where zero rows is legitimate |
| `alertOnNewLot` | boolean | `true` | Fires only when `monitorMode` is on and a destination is set |
| `alertOnBidChange` | boolean | `false` | Needs `watchlistLotIds` and `fetchDetails: true` |
| `watchlistLotIds` | string\[] | `[]` | Ids in this Actor's `"<assetId>-<accountId>"` form, e.g. `"13230-21430"` |
| `webhookUrl` | string | — | JSON POST per alert — Zapier, Make, n8n, Sheets. A failed POST warns, never fails the run |
| `slackWebhookUrl` | string | — | Slack card with photo, bid, location, seller, closing time |
| `emailRecipients` | string\[] | `[]` | Digest via the `apify/send-mail` Actor |
| **5. Advanced** | | | |
| `proxyConfiguration` | proxy | Apify datacenter | The site is not walled for this route — residential costs more and measurably buys nothing |
| `requestConcurrency` | integer | `4` | Clamped to 5. GovDeals' robots.txt asks for a 5-second crawl delay |
| `diagnose` | boolean | `false` | Dumps raw upstream JSON to the key-value store and **exits without billing you** |

***

### 💸 Pricing

**$0.003 per lot returned — $3 per 1,000.** Pay-per-event on the `lot-scraped` event, with no
monthly platform fee from this Actor and no charge for starting a run.

You are charged for **lots delivered to your dataset**, and never for:

- a lot that closed before the run reached it (dropped *before* enrichment),
- a lot removed by your `minBid` / `maxBid` filter,
- the same lot twice — duplicates are dropped before billing,
- any of the Actor's own retries, proxy traffic, category-menu lookups or bootstrap calls,
- a `diagnose: true` run, which writes no dataset rows at all.

Charging and delivery are **atomic** (`Actor.pushData(items, 'lot-scraped')`), so if you hit a
spend cap you get whole rows you paid for — never billed-but-missing rows.

| Run | Lots | Cost |
|---|---|---|
| Default smoke test | 50 | $0.15 |
| One state, enriched | 500 | $1.50 |
| Daily new-listings monitor | ~200 new/day | $0.60/day |
| Whole live catalogue (2026-08-12) | 26,193 | ~$79 |

`maxLots` is your hard cost cap (default **50**; set `0` for the full catalogue).

***

### 🔍 Honest limits

- **Seller contact is a 13.5% field, not a lead database.** `seller_contact_name` / `email` /
  `phone` are the agency's published surplus contact where the seller chose to publish one. On a
  vehicle-heavy commercial-reseller scope it is close to zero (the sample vehicle row above has
  all three null). This is a GovDeals publishing choice, not a scraping failure. If you need
  contactable rows, filter `sellerType: "1"` — government sellers publish a contact far more
  often — and use `agency_url` (43.5%) as the enrichment handle.
- **`make` is 45.5% and `vin_serial` is 15.5% on an unfiltered run.** Scope to vehicles and they
  go to 100%: a 30-lot `searchType: "category"`, `"Automobiles/Cars"` run on 2026-08-19 measured
  **make 30/30, model 30/30, model\_year 30/30, vin\_serial 30/30, meter\_reading 29/30**. There is
  no setting that makes a jewelry lot have a VIN.
- **`buy_now_price` is 2–6%** on the default scope and **30/30 on a `saleType: "4"` (Buy Now)
  run**. Most GovDeals lots are timed auctions; only Buy-Now / strike-price lots carry one.
- **`lot_number` is a Make-Offer field.** Measured per format on 2026-08-19: Online Auction 0/50,
  Buy Now 0/50, **Make Offer 47/50** — so an unfiltered run shows ~0% and `saleType: "5"` shows
  27/30. GovDeals also writes `-1` on some records when there is none, and this Actor returns
  `null` rather than passing the sentinel through as data.
- **`agency_url` (43.5%) is the agency's OWN website** (`webAddr`), published by only some
  sellers — 2/30 on a Texas run, 19/30 on a Buy-Now run. GovDeals separately carries a storefront
  slug for ~60% of lots, which this Actor does not currently emit; `agency` + `agency_id` are on
  100% of rows and are the reliable join keys.
- **A small number of lots have no detail record at all.** Open lots enriched 200/200, 60/60,
  50/50 and 25/25 across four measured runs, but in an earlier 20-lot sample GovDeals returned
  its zeroed placeholder (`assetId: 0`) for 2 of 20 — **18/20 = 90%**. That response repeats
  identically off-proxy and from a browser, so it is GovDeals, not a block. Those rows keep every
  listing field and are flagged `detail_fetched: false` — never a silent half-row.
- **No ZIP + radius search.** GovDeals' proximity parameters return nothing for anonymous
  callers, so this Actor does not pretend to support them. Use `states`, or filter
  `latitude`/`longitude` yourself; both are returned on 98.5% of rows.
- **No sold-price archive.** GovDeals stops publishing a lot's detail record once it closes, so
  `includeClosedLots: true` gives you the listing fields and a final bid, not a full sold record.
  Run `monitorMode` on a schedule if you want to accumulate comps over time.
- **One state per query.** GovDeals' state filter accepts a single value, so ten states cost ten
  walks. The cheap way to slice a region is to run unfiltered into a dataset and filter it yourself.
- **Cross-run dedupe needs `monitorMode`.** Within a run, duplicates are impossible (measured
  0.00% over 1,000 contiguous rows). Across runs, only monitor mode remembers.
- **No login, no API key, no CAPTCHA-solving service, no browser automation.** If GovDeals ever
  puts this route behind an account, this Actor will fail loudly rather than work around it.

***

### 🔌 How it works

GovDeals is an Angular single-page app — there is no server-rendered lot HTML to parse.
Everything the browser shows comes from three public JSON endpoints on Liquidity Services'
"maestro" service: a search endpoint (list + `x-total-count`), an asset endpoint (quantity,
condition, gallery, terms) and a live bid-box endpoint (bid, increment, buyer's premium, views).
The Actor calls the same three, anonymously, with **no login and no cookie**, reading the site's
own published web key out of its JS bundle on every run so a key rotation heals itself instead of
breaking your schedule.

**Transport, measured through the Apify proxy — not assumed:**

| Check | Result |
|---|---|
| Search, 20 **contiguous** pages × 50 rows, plain Apify **datacenter** proxy | **20/20 = 100%** |
| Uniqueness on those 1,000 rows | **1,000 unique, 0 duplicates — 0.00%** |
| Full-Actor contiguous walk, 1,000 lots over 10 pages | **1,000 unique, 0 duplicates — 0.00%** |
| Live bid box, 20 real lots | **20/20 = 100%** |
| Asset detail, 200 open lots (default scope) | **200/200 = 100%** |
| Public-config bootstrap (homepage + JS bundle) | **5/5 = 100%** |

No residential proxy, no session pinning, no browser — so runs are fast and cheap. A default
50-lot enriched run completes in ~30 seconds and ~103 HTTP calls (re-measured 2026-08-12: 38s,
103 calls); 1,000 listing-only lots take 29 seconds and 12 calls.

**Re-listed lots are handled correctly.** GovDeals re-lists unsold lots under an incremented
auction round, and the bid-box endpoint will happily answer for the *wrong* round if you ask
for the wrong one — returning the original round's price and closing date. (Asset `3/32136`:
round 1 → $7,100, closes 14 Jul; round 2 → $10,200, closes 12 Aug, which is what the site
shows.) This Actor always asks for the lot's current round and discards any answer that comes
back for a different one. A 50-lot sample spanning auction rounds 1–13 cross-checked against a
fresh live search: **0 mismatches** on closing date, and the only two bid deltas were lots
that took new bids in the seconds between the two reads.

#### Closed lots, and why the filter matters

GovDeals leaves a lot in its **open** search index for a while after bidding closes, and its
asset endpoint stops publishing that lot's detail record once it does. With the default
closing-soonest sort, a run that lands on the hour would otherwise hand you a page of dead
lots you cannot bid on — measured on a real run at `23:00:18Z` where **all 50** closing-soonest
rows had closed at `23:00:00Z` and only 12 still had a detail record.

So this Actor drops already-closed lots **before** enrichment: you are not billed for them,
and it keeps paginating to fill your `maxLots` with live ones. Verified across 1,000 lots:
**0 rows closed at their own scrape time.**

#### Uniqueness

Measured on **contiguous** page walks, because sparse offsets prove nothing: 20 contiguous search
pages × 50 rows gave **1,000 unique of 1,000 — a 0.00% duplicate rate**, and a full-Actor walk of
1,000 lots over 10 pages gave the same. Deduplication is keyed on `lot_id`
(`<assetId>-<accountId>`) and happens **before** enrichment and before billing, so the dataset —
and your bill — never holds the same lot twice. `displayRows` is held constant for a whole scope
on purpose: GovDeals offsets by page × displayRows, so shrinking it on the last page re-reads
rows you already have (measured: a 20-row final page came back 20/20 duplicates).

#### When a run fails

- **Zero rows throws.** A green-but-empty run is the classic broken-scraper failure, so the
  Actor errors with the scope, the GovDeals match count, how many lots were closed, how many
  were filtered out and the HTTP call counts.
- **A category name that matches nothing** is logged and skipped; if *none* of them resolve, the
  run stops rather than silently scraping the whole catalogue.
- **A non-govdeals.com URL in `startUrls`** is warned about and skipped, not guessed at.
- **A bid box answering for the wrong auction round** is discarded rather than used.
- **Detail fill below 60%** raises a warning naming the likely cause, so null columns are never
  mistaken for a scraper fault.
- **`diagnose: true`** dumps the raw upstream JSON and exits without writing rows or billing you.
- **The one legitimate zero:** `monitorMode` reporting no new lots since the last run.

***

### 👥 Who buys this

- **Used-equipment and heavy-plant dealers** — `make` + `model` + `meter_reading` + `end_date`
  on a category-scoped monitor is a daily buy-list; `buyer_premium_pct` is what makes the
  landed-cost maths correct.
- **Fleet and municipal buyers** — `sellerType: "1"` plus `states` gives every unit a public
  agency is disposing of within driving distance, with `latitude`/`longitude` for routing.
- **Resale arbitrage sellers (eBay / Facebook Marketplace / equipment brokers)** — `watcher_count`
  and `view_count` against `end_date` surface the lots nobody is watching that close today.
- **Auction aggregators and comparison sites** — one row shape across GovDeals plus the sibling
  Actors below, with ISO dates and numeric money, is a feed you can merge without a mapping layer.
- **Scrap and metals traders** — condition codes `S` (Scrap) and `NR` (Needs Repair) with
  `quantity_unit: "Lot"` are the bulk-disposal listings.
- **Procurement and asset-disposal analysts** — `agency` + `category` + `current_bid` over time
  is a defensible recovery-rate benchmark for what public bodies actually get for surplus.

***

### 🔗 Sibling Actors — same row shape

| Actor | What it is | Why you would use it instead |
|---|---|---|
| [GSA Auctions Scraper](https://apify.com/scrapersdelight/gsa-auctions-scraper) | US **federal** surplus | GovDeals is states, counties, cities and schools; GSA is the federal government. Different sellers, no overlap |
| [PublicSurplus Scraper](https://apify.com/scrapersdelight/publicsurplus-scraper) | The other big municipal surplus marketplace | Many agencies list on one or the other, not both — run both for real coverage |
| [Municibid Scraper](https://apify.com/scrapersdelight/municibid-scraper) | Municipal surplus, smaller-town skew | Reaches townships and school districts that never list on GovDeals |
| [K-BID Auction Scraper](https://apify.com/scrapersdelight/k-bid-scraper) | Upper-Midwest online auctions | Regional depth GovDeals does not carry |
| [Equip-Bid Auction Scraper](https://apify.com/scrapersdelight/equip-bid-scraper) | Midwest equipment & estate auctions | Same |

GovDeals is where you go for **government surplus with a buyer's premium and demand counts**;
GSA is where you go for **federal** disposals. If you only run one, run the one whose sellers
are within driving distance of your yard — surplus is a pickup-only business.

***

### ❓ FAQ

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

No. Every endpoint it touches is the anonymous public one the site itself calls for a
logged-out visitor. No account, no API key, no cookie, no CAPTCHA solving.

#### Can I paste a URL from the GovDeals website?

Yes — that is the `startUrls` field, and it is the fastest way in. Search pages, category, state
and seller searches, and single lot pages all work, and you can mix them in one list.

#### Can I get the whole catalogue in one run?

Yes — `searchType: "all"`, `maxLots: 0`. That was 26,193 lots on 2026-08-12 and about 262 search
pages. Leave `fetchDetails: true` on only if you want the full record, since it adds two calls
per lot. Raise the run timeout for big sweeps.

#### How much does 1,000 lots cost?

$3.00, at $0.003 per lot returned. The whole live catalogue is about $79.

#### Do I get lots that have already ended?

No, not by default — they are dropped before you are billed for them. Set
`includeClosedLots: true` if you want them.

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

No. Closed lots, `minBid`/`maxBid` rejects and duplicates are all removed before billing, and a
`diagnose: true` run bills nothing at all.

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

Within a run, no: 1,000 unique of 1,000 on a contiguous walk. Across runs, yes, unless you turn
on `monitorMode`, which remembers what it has already returned for that exact scope.

#### Does it need a proxy?

It uses the default Apify datacenter proxy and that is enough — 20/20 on contiguous search pages
and 20/20 on live bid boxes. Residential costs more and measurably buys nothing here.

#### Why does `current_bid` differ from the site by a few dollars?

Because it is a live auction. Rows are a snapshot at fetch time; lots closing within minutes
move between your run and your read.

#### How fresh is the bid?

With `fetchDetails: true` the bid comes from the per-lot live bid box fetched during the run,
which is the same number the site's bid widget shows. With `fetchDetails: false` it comes from
the search row.

#### Does `time_left_seconds` keep counting?

No — it is computed at `scraped_at`. Use `end_date` (absolute UTC) for anything you schedule on.

#### Can I filter by ZIP + radius?

Not reliably — GovDeals' proximity parameters return nothing for anonymous callers, so this
Actor does not pretend to support them. Use `states`, or filter `latitude`/`longitude`
yourself; both are returned on 98.5% of rows.

#### What is `buyer_premium_pct`?

The seller's buyer premium as a percentage, straight from GovDeals' bid box (commonly
9.5–15%). It is what turns a headline bid into a landed cost, and most surplus scrapers do not
return it. Requires `fetchDetails: true`.

#### Are `view_count` / `visitor_count` / `watcher_count` useful?

They are GovDeals' own demand signals for a lot — how many times it has been opened, by how
many distinct visitors, and how many people are watching it. A high watcher count on a lot
closing soon means competition; a zero-watcher lot closing today is where the arbitrage is.

#### Why are some fields empty?

Because GovDeals never published them for that lot. `make`/`model`/`VIN`/`meter` exist on
vehicles and plant only; seller contact exists where the agency chose to publish one. The fill
tables above give the measured rate for every field, and every run logs its own.

#### Why do some titles have a missing character?

GovDeals stores a handful of dashes as an un-decodable byte in its own database. Rather than
invent a character, this Actor strips it and collapses the whitespace.

#### Sealed-bid and Buy-Now lots?

Included. `sale_type` tells you the format, and `saleType` in the input filters to one.

#### Canadian lots?

Yes — GovDeals Canada lots share the catalogue. Province codes (`"ON"`, `"AB"`) work in
`states`, and `currency` distinguishes CAD from USD rows.

#### Can I schedule it?

Yes. Use `monitorMode: true` with an Apify Schedule — hourly for bid movement on a watchlist,
daily for new inventory — and wire `slackWebhookUrl` or `webhookUrl` into your own tools.

#### Can I export to CSV or Excel?

Yes — the dataset exports to CSV, Excel, JSON, XML or an API feed like any Apify dataset, and
the Actor ships a table view with the columns that matter for bidding.

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

Set `diagnose: true`. The Actor fetches two pages plus one lot, dumps the raw GovDeals JSON to
the key-value store (`DEBUG_SEARCH_ROWS`, `DEBUG_DETAIL`, `DEBUG_BIDBOX`), reports whether
pagination overlapped, and exits without writing dataset rows or charging you for them.

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

No. A green-but-empty run is the classic broken-scraper failure, so this Actor throws with the
scope, the GovDeals match count and the HTTP call counts in the message. Monitor mode is the
one exception: "no new lots since last run" is a legitimate zero.

***

### ⚖️ Legal & fair use

GovDeals lists surplus property sold by public agencies (cities, counties, states, school
districts, universities) and their commercial remarketers; the listings are public and intended
to be seen by the widest possible audience of bidders.

**What is read:** the site's own public, logged-out JSON endpoints and the public JS bundle at
the site root, at a concurrency of 5 or less. **What is not touched:** the paths GovDeals'
[robots.txt](https://www.govdeals.com/robots.txt) disallows (`/account/`, `/assets/`), any
logged-in page, and any anti-bot challenge — this Actor does not log in, does not solve
CAPTCHAs and collects no data behind any authentication.

**Personal data:** rows carry no bidder identities — high bidders appear only as GovDeals'
opaque numeric id, never a name or username. The only contact details returned are the selling
agency's own published surplus contact (13.5% of rows), which is business contact information
published by a public body for the purpose of being contacted about that lot. If you use it for
outreach, you are responsible for GDPR/CCPA, CAN-SPAM and any applicable state rules.

You are responsible for complying with
[GovDeals' Terms of Service](https://www.govdeals.com/terms) and with applicable law in your
jurisdiction. Verify every lot on GovDeals before bidding — auction data changes by the second.

GovDeals® is a trademark of Liquidity Services, Inc. This Actor is an independent tool and is
not affiliated with, endorsed by, or sponsored by GovDeals or Liquidity Services.

***

### 💬 Feedback

Found a missing field, or want a filter that is not here? Open an issue on the **Issues** tab —
include the run id and, if a run looked wrong, the output of `diagnose: true`.

# Actor input Schema

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

The fastest way in: go to govdeals.com, apply whatever filters you like, then copy the URL out of your address bar and paste it here. Search pages (https://www.govdeals.com/search?kWord=forklift), category, state and seller searches, and single lot pages (https://www.govdeals.com/asset/13230/21430) all work, and you can mix them in one list. If this is not empty it WINS and the picker below is ignored. Read from the URL: keyword, category, state, seller account and sale type. Not read: the URL's own price and sort parameters — set those with Minimum/Maximum bid and Sort, which apply to every scope in the run. Leave empty to use the picker below.

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

Used only when 'Paste GovDeals URLs' above is empty. 'All open lots' walks GovDeals' whole live catalogue (26,193 open lots measured 2026-08-12; every run logs the live count). 'By state' runs one server-side query per state. 'By category' expands a GovDeals category (code or name) to every child code, which matters because a lot only carries its own leaf code. 'By keyword' runs the site's own search. 'By seller' takes GovDeals account ids. 'Specific lot URLs' resolves lots you paste into the Lot URLs field.

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

Two-letter codes or full names — "TX", "FL", "California", "ON". GovDeals filters state SERVER-SIDE but accepts only one state per query, so each entry costs one extra request per page of results. Canadian provinces work too (GovDeals Canada lots share the catalogue). Ignored unless the source above is 'By state'.

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

GovDeals category codes or names, e.g. "36" or "Heavy Equipment and Construction", "Material Handling Equipment", "Jewelry", "Buses". Names are matched case-insensitively against GovDeals' live category menu and EXPANDED to every child code — measured: "Material Handling Equipment" + 36 resolves to 39 codes. Filtering on a family code alone silently misses almost everything (code 206 "Clothing, Jewelry, and Accessories" returns 85 lots; its child 56 "Jewelry" returns 1,096). If a name matches nothing the run logs it and skips it. Ignored unless the source above is 'By category'.

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

Typed exactly as you would into the GovDeals search box, e.g. "forklift", "dump truck", "generator". Each term is walked page by page until the lot cap is hit, so two terms take roughly twice as long as one. Ignored unless the source above is 'By keyword'.

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

Numeric GovDeals account ids — the second number in a lot URL (https://www.govdeals.com/asset/<assetId>/<accountId>) and the account\_id field in this Actor's own output. All ids ride in ONE server-side query, so ten sellers cost the same as one. Ignored unless the source above is 'By seller'.

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

Full GovDeals lot URLs, e.g. https://www.govdeals.com/asset/13230/21430 . The legacy ?itemid=…\&acctid=… form and a bare "assetId/accountId" pair are accepted too. Each lot is resolved straight off the asset endpoint — one request per lot, no search walk, live bid included — which makes this the cheapest way to re-price a watchlist. You can also just paste these into 'Paste GovDeals URLs' at the top and ignore the picker entirely.

## `sellerType` (type: `string`):

GovDeals lists both public agencies and the commercial remarketers that resell for them. Filtered SERVER-SIDE, so narrowing here costs you nothing and does not slow the run down.

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

Most GovDeals lots are timed online auctions; the rest are fixed-price or offer formats. Filtered SERVER-SIDE. Note that buy\_now\_price is only ever set on Buy Now / strike-price lots — measured at 6% of a default 200-lot run and 20.5% of another, purely because of the sale-format mix in the scope.

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

Narrow to lots closing today (bargain-hunting / sniping) or to lots listed since the last sweep (new-inventory monitoring). Filtered SERVER-SIDE. Leave on 'Any time' for a scheduled monitor — Monitor mode below already tracks what is new, and stacking both narrows you twice.

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

Drop lots whose current bid is below this. Applied client-side AFTER the search, so it does not reduce what the Actor fetches — it keeps your dataset clean, and rows it removes are never charged to you. Lots with no bid yet are always kept, because a $0 lot is the one you wanted to see.

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

Drop lots whose current bid is above this. Applied client-side AFTER the search. Lots with no bid yet are always kept. Rows removed here are never charged to you.

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

OFF (default) drops lots whose closing time has passed BEFORE they are enriched, so you never pay for a lot you cannot bid on. This matters more than it sounds: GovDeals keeps just-closed lots in its open search index for a while AND stops serving their detail record, and on a real run at 23:00:18Z all 50 closing-soonest rows had closed at 23:00:00Z. Turn ON to build a sold-comps history — but expect null quantity/condition/gallery on those rows, because GovDeals no longer publishes them.

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

Server-side sort. 'Closing soonest' (the default) is what you want for sniping and for a monitor; 'Newest listings' for fresh-inventory sweeps; 'Highest bid' to skim the big-ticket lots first. This decides WHICH lots a capped run gets, so it matters most when Max lots is small relative to the scope.

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

ON (default): two extra requests per lot add quantity, condition, VIN, meter reading, seller contact, inspection/payment/pickup terms and the FULL photo gallery, plus the live bid box — bid increment, buy-now price, BUYER'S PREMIUM %, view/visitor/watcher counts and reserve state. Measured on 200 open lots: 200/200 enriched. OFF: search rows only — 1,000 lots in 29 seconds and 12 HTTP calls instead of ~103 calls for 50, and you still get title, category, current bid, bid count, closing time, seller, location and one photo. The price per lot is the SAME either way, so this buys you speed, not money.

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

Hard cap on lots returned this run — and therefore your cost cap, since you are billed per lot returned. Duplicates and lots removed by the closed/bid filters are dropped before billing and never count against it. Set 0 for no cap; the full live catalogue was 26,193 lots on 2026-08-12, which at $0.003/lot is about $79.

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

Remembers every lot id it has already returned FOR THIS EXACT SCOPE in a named key-value store, so a scheduled run outputs — and bills — only genuinely new listings. Verified: run 1 returned 12 new of 12, run 2 returned 0 new of 12, and a different keyword got its own independent seen-set. Ended lots age out of the memory a day after they close. This is the one mode where a zero-row run is legitimate and does not throw.

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

Send a webhook / Slack / email alert for every newly seen lot. Used only when Monitor mode is on AND at least one destination below is set; otherwise it does nothing.

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

Alert when the current bid changes on a lot listed in Watchlist lot ids. Used only when Monitor mode is on. Needs Open each lot ON to see a live bid.

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

Lot ids in this Actor's own "<assetId>-<accountId>" form, e.g. "13230-21430" — it is the lot\_id field of any row this Actor has already returned. Used only by 'Alert when a watched lot's bid moves'.

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

Optional. Every alert is POSTed here as JSON — wire it into Zapier, Make, n8n, Google Sheets or your own endpoint. A failed POST is logged as a warning and never fails the run.

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

Optional. Alerts are posted as a Slack card with the lot photo, current bid, location, seller and closing time.

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

Optional. Email addresses to send the new-lot digest to (sent through the apify/send-mail Actor, which runs on your account).

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

GovDeals is NOT walled for this route. Measured THROUGH the Apify proxy: 20 contiguous search pages on plain Apify DATACENTER returned 20/20, and 20 live bid boxes returned 20/20 — so the default is the cheap datacenter rung and residential bandwidth is not needed. Turning residential on here will cost you more and measurably buy you nothing.

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

Parallel requests for the detail and bid-box fetches. Kept low on purpose to stay polite to GovDeals, whose robots.txt asks for a 5-second crawl delay; values above 5 are clamped to 5.

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

Fetch two pages plus one lot, dump the raw GovDeals JSON to the key-value store (DEBUG\_SEARCH\_ROWS / DEBUG\_DETAIL / DEBUG\_BIDBOX), report whether pagination overlapped, then exit WITHOUT writing dataset rows — so it never bills you. Use this first if a run looks wrong, and paste the output into an issue.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.govdeals.com/search?kWord=forklift"
  ],
  "searchType": "all",
  "states": [
    "TX"
  ],
  "categories": [
    "Heavy Equipment and Construction"
  ],
  "keywords": [
    "forklift"
  ],
  "sellerAccountIds": [],
  "lotUrls": [],
  "sellerType": "any",
  "saleType": "any",
  "timeWindow": "any",
  "includeClosedLots": false,
  "sortBy": "closing",
  "fetchDetails": true,
  "maxLots": 50,
  "monitorMode": false,
  "alertOnNewLot": true,
  "alertOnBidChange": false,
  "watchlistLotIds": [],
  "emailRecipients": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestConcurrency": 4,
  "diagnose": false
}
```

# Actor output Schema

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

One row per GovDeals lot: title, seller agency, category, current bid, bid count, bid increment, buyer's premium, buy-now price, closing time, quantity, condition, VIN, meter, city/state, seller contact, photo gallery and lot URL.

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

Rows seen, unique lots, measured in-run duplicate rate, detail/bid-box fill 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 = {
    "searchType": "all",
    "fetchDetails": true,
    "maxLots": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/govdeals-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 = {
    "searchType": "all",
    "fetchDetails": True,
    "maxLots": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/govdeals-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 '{
  "searchType": "all",
  "fetchDetails": true,
  "maxLots": 50
}' |
apify call scrapersdelight/govdeals-auction-scraper --silent --output-dataset

```

## MCP server setup

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