# PublicSurplus Scraper - Government Surplus Auction Data API (`parseforge/publicsurplus-scraper`) Actor

Extract open PublicSurplus government surplus auctions: live bid count and price, closing time, selling agency, pick-up address, photos, attached documents, condition and vehicle specs including VIN, mileage and an optional NHTSA VIN decode.

- **URL**: https://apify.com/parseforge/publicsurplus-scraper.md
- **Developed by:** [ParseForge](https://apify.com/parseforge) (community)
- **Categories:** E-commerce, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.23 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

[![ParseForge](https://raw.githubusercontent.com/ParseForge/apify-assets/main/banner.jpg)](https://apify.com/parseforge?fpr=vmoqkp)

### PublicSurplus Scraper - Government Surplus Auction Data API

Scrape open government auctions from PublicSurplus. Every run returns a **20-field base row** — id, title, price, currency, region, the full schedule, the lot type, the category, the condition grade and the selling agency — for one flat price. On top of that sit **nine optional data blocks** covering the other 68 fields: live bidding, the agency profile, the pick-up address, the sale terms, the item's own spec sheet, an NHTSA VIN decode, photos, documents and the description. **Each one is off by default: if you do not tick it, you do not receive it and you are not charged for it.** No login, no API key, no cookies. Export to CSV, JSON, Excel, or XML.

PublicSurplus is where roughly **5,700 open lots** from US and Canadian cities, counties, school districts, universities, police departments and state agencies sit at any moment. It publishes no API. This Actor reads the same pages a buyer sees and turns them into one fixed schema.

| Who uses it | What they scrape PublicSurplus for |
| --- | --- |
| Dealers and resellers | Fleet vehicles and heavy equipment with VIN, odometer and running condition, filtered to lots closing today |
| Auction aggregators | A deduplicated feed of every open lot in one schema, keyed on a stable auction id |
| Fleet and asset managers | What comparable units actually sell for, by state, agency and category |
| Municipal procurement teams | What peer agencies are disposing of, and on what payment and pick-up terms |
| Price and market researchers | Bid counts, increments and reserve state across 232 categories and 68 regions |

### What it does

This Actor collects PublicSurplus auctions by keyword, category, state, agency, ZIP radius or exact URL, and returns each lot as a flat row. The 🔓 blocks below are optional and off unless you ask for them.

🔓 **The selling agency, not just its name.** (`includeAgencyProfile`) `agencyId`, the agency's storefront URL, its logo, and a link to every other lot it has open — so one row turns into a whole agency's catalogue.

🔓 **The pick-up address split into columns.** (`includePickupLocation`) Location name, street, city, state and ZIP as separate fields, plus PublicSurplus's map link. You can plan a collection route straight off the dataset.

🔓 **Live bidding state.** (`includeBidding`) `bidCount`, `minimumBid`, `bidIncrement`, the masked handle of the leading bidder with their years on the platform, and whether a reserve exists and has been met.

🔓 **The agency's own vehicle block.** (`includeItemSpecs`) `vin`, `mileage`, `year`, `make`, `model`, `engine`, `transmission`, `tires` and `runningCondition` as typed columns — plus the CARFAX link PublicSurplus prints beside the VIN.

🔓 **An NHTSA VIN decode.** (`includeVinDecode`) Tick it and every 17-character VIN gains 16 more columns from the US government's public vPIC database: body class, fuel type, cylinders, displacement, drive type, GVWR class, assembly plant.

🔓 **Real-estate lots too.** (`includeItemSpecs`) Parcel number, property type, zoning, lot size in acres and building size in square feet come back as typed fields on Real Estate lots.

🔓 **Every photo, full resolution.** (`includeImages`, `includeAttachments`) `imageUrls` holds the agency's uploads in order, not the listing thumbnail. Attached documents — titles, inspection reports, site maps — come back as name-and-URL pairs.

🏷️ **All three lot types, in the base row.** Ordinary bid auctions, `fixed-price` buy-it-now lots with their asking price, and multi-unit `dutch` auctions with the quantity on offer.

Results export to CSV, JSON, Excel, or XML, or stream from the API.

### What you can do with PublicSurplus data

🚗 **Source fleet vehicles by the numbers.**

Filter to Motor Pool, `onlyWithVin`, mileage under your ceiling and `endingWithinHours` of 24, and you get a shortlist of vehicles closing today with the VIN already decoded.

📈 **Track what surplus actually sells for.**

Run it on a schedule and key on `auctionId`. `bidCount` and `currentPrice` climbing across runs is a live price curve; a lot that disappears has closed.

🗺️ **Plan a collection route.**

`pickupCity`, `pickupState`, `pickupZip` and the ZIP-radius filter let you buy only what you can physically collect.

🔔 **Watch for new listings.**

`startedWithinHours` asks PublicSurplus for lots that opened in the last hour, day or week. That is a new-listing monitor with one field and no diffing.

🏢 **Follow specific agencies.**

Put their `agencyId` values in `agencyIds` and the Actor walks each agency's whole open catalogue, 50 lots a page.

### Why choose this scraper

| What you get | |
| --- | --- |
| 88 fields per lot | Every field the public auction page publishes, verified against live pages rather than guessed. Twenty of them are the base row; the rest arrive block by block, as you ask for them. |
| Bids, not just prices | `bidCount`, `bidIncrement`, `minimumBid`, the leading bidder's masked handle and platform tenure, and reserve state. |
| The agency block | `agencyId`, storefront, logo, full pick-up address, accepted payment methods, card ceiling, deposit rule and the terms document. |
| Vehicle specs as columns | VIN, mileage, year, make, model, engine, transmission, tires and running condition — from the agency's own form, not parsed out of prose. |
| Nothing billed by surprise | A run left at its defaults charges exactly one per-row price. Nine optional blocks are each a checkbox and each its own line on the bill. |
| 23 filters that cut the bill | Thirteen go to PublicSurplus with the query; ten more run before the row is written. Rejected rows are never charged — and the filters read the complete row, so you can filter on bids, photos, documents or a VIN without buying those columns. |
| 232 categories, 68 regions | All 28 top categories and 204 sub-categories, 50 US states, DC, four US territories and 13 Canadian provinces and territories — every region the site lists — by name or by id. |
| No proxy, no login | PublicSurplus answers plain requests. The Actor runs proxy-free by default, so a run costs you nothing in proxy traffic. |
| Honest empties | A field the agency did not fill in is absent, never a guessed value and never a null. |
| Four export formats | CSV, JSON, Excel, and XML, from the dashboard or the API. |

### How it compares

The competitor columns below reflect what each Actor's own public listing states, not a test run of it.

| Feature | ParseForge | scrapersdelight | lulzasaur | crawlerbros |
| --- | --- | --- | --- | --- |
| Fields per lot | 88 | Lot, bid, location, close time | Title, bid, location, category, close time, URL, images | Not stated |
| Bid count, increment and minimum | Yes | Not stated | Not stated | Not stated |
| High bidder handle and tenure | Yes | No | No | No |
| Reserve price state | Yes | No | No | No |
| Selling agency id and pick-up address | Yes | Location only | Location only | Not stated |
| Vehicle VIN, mileage and condition | Yes | Not stated | Not stated | Not stated |
| NHTSA VIN decode | Yes, 16 columns | No | No | No |
| Real-estate parcel, zoning and lot size | Yes | No | No | No |
| Attached documents | Yes | No | No | No |
| Fixed-price and Dutch lots flagged | Yes | Not stated | Not stated | Not stated |
| New-listing monitor | `startedWithinHours` filter | Yes, with Slack/email/webhook alerts | Not stated | Not stated |
| Base price per result | $0.007 | $0.003 | $0.005 | $0.005 |
| Optional blocks you can switch off | 9, each its own charge | No | No | No |

This Actor is the dearest of the four per row, and deliberately so: the base row alone carries fields the others do not return at all, and the deep data — bids, agency, address, documents, spec sheet, VIN decode — is priced block by block instead of being folded into everybody's bill. If all you need is a title-and-price feed, the cheaper Actors will do it more cheaply.

If you want **alerting** — a scheduled monitor that pushes new lots to Slack, email or a webhook — scrapersdelight's Actor does that and this one does not.

### What a lot looks like

A run left at its defaults returns this — the base row, twenty fields, one flat price. Real record, unedited. (`categoryId` and `categoryName` are the two that only appear when the run browsed a category; a keyword search returns eighteen.)

```json
{
  "auctionId": "4066151",
  "url": "https://www.publicsurplus.com/sms/auction/view?auc=4066151",
  "title": "ADOT - D790 - 2002 FORD F450 TRUCK 1.5 TON 5TH WHEEL DRW 4X4",
  "scrapedAt": "2026-08-20T17:27:58.087Z",
  "currentPrice": 7300,
  "currency": "USD",
  "state": "AZ",
  "auctionType": "bid",
  "condition": "UNKNOWN",
  "agencyName": "ADOT",
  "categoryId": 4,
  "categoryName": "motor pool",
  "startsAt": "2026-08-18T03:00:00.000Z",
  "endsAt": "2026-08-22T03:00:00.000Z",
  "startedAtText": "Aug 17, 2026 09:00 PM MDT",
  "endsAtText": "Aug 21, 2026 09:00 PM MDT",
  "timeLeftSeconds": 120722,
  "isEnded": false,
  "mightExtend": true,
  "thumbnailUrl": "https://d37qv0n5b4mbzm.cloudfront.net/sms/docviewer/cdnmainaucdoc/thumb-b/4066151/71388322"
}
```

Tick the blocks you want and the same row grows. With all nine on, a fleet vehicle comes back with 70 fields:

```json
{
  "auctionId": "4064156",
  "title": "2020 Chevrolet Tahoe 4x2",
  "currentPrice": 5600,
  "bidCount": 35,
  "minimumBid": 5700,
  "bidIncrement": 100,
  "highBidder": "F******1",
  "highBidderYears": 0,
  "agencyId": "294914",
  "agencyUrl": "https://www.publicsurplus.com/sms/scottsdale,az/browse/home",
  "agencyAuctionsUrl": "https://www.publicsurplus.com/sms/list/current?orgid=294914",
  "pickupStreet": "9191 E San Salvador Dr",
  "pickupCity": "Scottsdale",
  "pickupState": "AZ",
  "pickupZip": "85258",
  "paymentMethods": ["Visa", "MasterCard", "Discover"],
  "creditCardLimitUsd": 4000,
  "bidDeposit": "REQUIRED",
  "termsUrl": "https://www.publicsurplus.com/sms/docviewer/aucterms?auc=4064156",
  "year": 2020,
  "make": "Chevrolet",
  "model": "Tahoe",
  "mileage": 158773,
  "vin": "1GNLCDEC8LR231706",
  "engine": "5.3 Li 8V",
  "transmission": "6 Automatic",
  "runningCondition": "Starts, Runs, Drives",
  "carfaxUrl": "https://secure.carfax.com/creditCard.cfx?partner=DVE_B&vin=1GNLCDEC8LR231706",
  "vinBodyClass": "Sport Utility Vehicle [SUV]/Multipurpose Vehicle [MPV]",
  "vinFuelType": "Gasoline",
  "vinEngineCylinders": 8,
  "vinDisplacementL": 5.3,
  "vinGvwr": "Class 2E: 6,001 - 7,000 lb (2,722 - 3,175 kg)",
  "vinPlantCountry": "UNITED STATES (USA)",
  "imageCount": 25,
  "imageUrls": ["https://d37qv0n5b4mbzm.cloudfront.net/sms/docviewer/cdnaucdoc/img/4064156/71341883"],
  "attachmentCount": 0,
  "description": "6,001-7,000 GVW Sport Utility - 3/4 Ton Public Safety ...",
  "descriptionFormat": "text"
}
```

### Configure the run

| Setting | What it does |
| --- | --- |
| `keyword` / `keywords` | Word or phrase to search for. PublicSurplus matches it against the title **and the full description**, so `truck` also finds a cabinet whose description mentions a hand-truck. Each extra keyword is its own search, merged and deduplicated. |
| `category` / `categoryIds` | One of 28 categories or 204 sub-categories. Accepts the name (`motor pool`), a sub-category (`motor pool > truck`, or just `truck`), or the numeric id. |
| `region` / `regions` | State or province. Accepts the name (`California`), the code (`CA`), or the site's own `all,ca`. |
| `agencyIds` | Walk specific agencies' full catalogues instead of searching. Use the `agencyId` from any row. |
| `auctionUrls` | Exact lots to fetch, skipping discovery. Accepts a full auction URL or the bare number. |
| `maxItems` | Ceiling on rows written. Not a target — a narrow filter returns fewer. |
| `maxPages` | How deep discovery may page into one search. A search page holds 25 rows, an agency page 50. |
| `sortBy` / `sortDescending` | Closing soonest, price, auction id, or title. Sent to PublicSurplus; with several keywords, categories or regions the merged set is sorted again before rows are written. |
| `listingOnly` | Skip the lot's own page for a faster, shallower run — 13 fields straight off the listing, without the condition grade, agency name or start date. It does not change the price, and the page is read anyway if you tick a block or set a filter that needs it. |
| `descriptionFormat` | `text` or `html`, for when the description block is on. |
| `proxyConfiguration` | Off by default. PublicSurplus answers plain requests; enable only if your runs start getting rate-limited. |

#### Optional data blocks

The base row is always there. Everything else is a checkbox, **off by default**, with its own line on the bill. A block you do not tick is neither written to the dataset nor charged for.

| Switched on by | What it adds |
| --- | --- |
| `includeBidding` | `bidCount`, `openingPrice`, `minimumBid`, `bidIncrement`, `highBidder`, `highBidderYears`, `hasReserve`, `reserveMet`, `buyItNowPrice`, `quantity`. |
| `includeAgencyProfile` | `agencyId`, `agencyAlias`, `agencyUrl`, `agencyAuctionsUrl`, `agencyLogoUrl`. The agency's name is in the base row either way. |
| `includePickupLocation` | `pickupLocationName`, `pickupStreet`, `pickupCity`, `pickupState`, `pickupZip`, `mapUrl`. |
| `includeSaleTerms` | `paymentMethods`, `paymentNote`, `creditCardLimitUsd`, `shipping`, `bidDeposit`, `termsUrl`. |
| `includeItemSpecs` | `specs` plus the typed columns: `year`, `make`, `model`, `mileage`, `vin`, `engine`, `transmission`, `tires`, `runningCondition`, `bodyStyle`, `manufacturer`, `hours`, `carfaxUrl`, `parcelNumber`, `propertyType`, `lotSizeAcres`, `buildingSizeSqFt`, `zoning`. About 20% of lots carry a block. |
| `includeVinDecode` | The 16 `vin*` columns from NHTSA vPIC. Works on its own — you do not need `includeItemSpecs` to get them. |
| `includeImages` | `imageUrls` and `imageCount`. |
| `includeAttachments` | `attachments` and `attachmentCount`. About 6% of lots carry a document. |
| `includeDescription` | `description` and `descriptionFormat`. |

#### Narrow the run with filters

Every filter runs before the row is written, so **rows you filter out are never charged** — not even for the auction page the Actor had to read in order to apply the filter. The one thing a wide run still pays for is the `listing-page` events discovery spends finding candidates.

Filters read the **complete** row, before the optional blocks are stripped out. That means you can filter on data you are not buying: `onlyWithVin`, `onlyWithBids`, `minBidCount`, `onlyWithImages` and `onlyWithAttachments` all work with every block switched off. You pay for the rows that match, not for the columns used to find them.

Thirteen filters go to PublicSurplus with the query, so the lots they reject are never even downloaded:

| Filter | What it keeps |
| --- | --- |
| `keyword` / `keywords` | Lots whose title or description contains the term. |
| `category` / `categoryIds` | Lots in that category or sub-category. |
| `region` / `regions` | Lots in that state, territory or province. |
| `agencyIds` | Lots from those agencies. |
| `priceMin` / `priceMax` | Lots whose current price falls in the range. |
| `endingWithinHours` | Lots closing in 1, 6, 24, 120 or 240 hours — the five steps the site offers. |
| `startedWithinHours` | Lots that opened in the last 1, 24, 48 or 168 hours. |
| `zipCode` + `milesFromZip` | Lots within a radius of a US ZIP code, in the steps the site offers from 20 to 1,000 miles. |

Ten more run in the Actor, because PublicSurplus has no query parameter for them:

| Filter | What it keeps |
| --- | --- |
| `auctionTypes` | `bid`, `fixed-price` or `dutch`. Read off the listing badge, so a rejected lot never costs a detail fetch. |
| `states` | Lots in these two-letter regions. Cheaper than `regions` on a broad run, because it does not fan out into separate searches. |
| `titleExcludes` | Drops lots whose title or description contains any of these words. |
| `onlyWithBids` / `onlyWithoutBids` | Lots that have, or have not, been bid on. |
| `minBidCount` / `maxBidCount` | Lots inside a bid-count range. |
| `onlyWithImages` | Lots the agency published with at least one photo. |
| `onlyWithVin` | Lots carrying a valid 17-character VIN. |
| `onlyWithAttachments` | Lots with an attached document. |

#### Why discovery works the way it does

PublicSurplus's search endpoint is stateless: every filter has to ride on every request, page 2 included. The Actor sends the full query each time, which is why paging deep into a filtered search returns the filtered set rather than drifting back to everything.

Its listing pages give 25 rows each, and an unfiltered search runs about **230 pages** deep — roughly **5,750 open lots site-wide**, measured 20/08/2026. Lots open and close all day, so the Actor reads the pager off the page instead of assuming a page count, and stops on the first empty page. A single search therefore reaches the whole site; a category or region search reaches all of it several times over.

Closed auctions are **not** covered. PublicSurplus answers `401` for a closed lot unless you are logged in, so this Actor returns open lots only. A lot that closes mid-run is logged and skipped, never billed.

### Pricing

This Actor is **pay per event**, and a run left at its defaults has exactly one per-row price. Every optional data block is **off by default**: if you do not tick it, you do not receive it and you are not charged for it. Nothing is billed for rows a filter rejected.

| Event | Price | When it fires |
| --- | --- | --- |
| `result-item` | $0.007 | One auction row. Covers the base row: id, URL, title, price, currency, region, the full schedule, lot type, category, condition grade, selling agency name and thumbnail — and the auction page read to get them. |
| `listing-page` | $0.002 | One search or agency listing page read during discovery — 25 rows on a search, 50 on an agency. Charged per page, whatever the filters keep. |
| `apify-actor-start` | $0.02 | Once when the run starts, per GB of memory. |

**Optional, and off unless you ask for them:**

| Event | Price | Switched on by |
| --- | --- | --- |
| `bid-data` | $0.0015 | `includeBidding` — bid count, opening and minimum bid, increment, high bidder, reserve state, buy-it-now price, Dutch quantity. |
| `agency-profile` | $0.0008 | `includeAgencyProfile` — agency id, slug, storefront, logo, and a link to its other open lots. |
| `pickup-location` | $0.0008 | `includePickupLocation` — location name, street, city, state, ZIP and the map link. |
| `sale-terms` | $0.0006 | `includeSaleTerms` — payment methods, card ceiling, shipping, bid deposit, terms document. |
| `item-specs` | $0.0012 | `includeItemSpecs` — the agency's attribute block, typed. ~20% of lots carry one. |
| `vin-decode` | $0.0015 | `includeVinDecode` — 16 NHTSA vPIC columns. One extra request per vehicle. |
| `image-set` | $0.0008 | `includeImages` — every full-resolution photo URL. |
| `attachment-docs` | $0.0006 | `includeAttachments` — attached documents. ~6% of lots carry one. |
| `description-text` | $0.0008 | `includeDescription` — the agency's full lot description. |

A default run costs **$0.007 per lot**, or $7.00 per 1,000, plus $0.002 for each 25-row listing page discovery reads. With all nine optional blocks switched on it is **$0.0156** per lot, or $15.60 per 1,000 — and you only get there by ticking all nine yourself.

Measured on 20/08/2026, a run costs us $0.05 per 1,000 rows at worst — no proxy, no browser, no unblocker. The price is what keeps the Actor maintained, not what covers the traffic.

### Free users

Apify free-plan runs are capped at **50 auctions per run**. Everything else works the same: all 88 fields across the nine blocks, all 23 filters. Upgrade your Apify plan to lift the cap.

### Run it

1. Open the Actor in Apify Console and set a `keyword`, a `category`, a `region`, or any combination.
2. Set `maxItems` to the number of lots you want, and tick any optional blocks you need under **Optional data**.
3. Click **Start**, then export the dataset as CSV, JSON, Excel, or XML.

Or from the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/parseforge~publicsurplus-scraper/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"category":"motor pool","region":"Texas","onlyWithVin":true,"includeItemSpecs":true,"includeVinDecode":true,"maxItems":100}'
```

### Use with AI agents (MCP)

The Actor is exposed over Apify's MCP server, so an agent can call it as a tool:

```
https://mcp.apify.com/sse?actors=parseforge/publicsurplus-scraper
```

Ask things like *"find government pickup trucks on PublicSurplus in Texas closing today"* and the agent fills the input itself.

### Troubleshooting

**No auctions came back.**

The filters are ANDed. A keyword plus a category plus a region plus a bid-count floor can genuinely match nothing. Drop one filter at a time, and raise `maxPages` — a rare filter such as `auctionTypes: ["dutch"]` needs discovery to read deeper before it finds enough matches.

**Fewer rows than `maxItems`.**

`maxItems` is a ceiling. Discovery stops when the page budget is exhausted, and lots that closed between the listing page and the detail fetch are skipped. The log prints how many were filtered and how many had gone.

**A keyword search returns things that do not look like the keyword.**

PublicSurplus searches the full description, not just the title. A cabinet whose description says "bring a hand-truck" is a genuine hit for `truck`. Use `titleExcludes` to trim, or run `includeDetails` and filter the description yourself.

**A column I expected is not in the output.**

Most of the 88 fields belong to one of the nine optional blocks and are off until you tick them. `bidCount` needs `includeBidding`, `vin` needs `includeItemSpecs`, `pickupZip` needs `includePickupLocation`, and so on — the **Optional data blocks** table above maps every field to its checkbox. The filters keep working regardless, so `onlyWithVin` still finds the vehicles even with every block off.

**Why is `openingPrice` missing on most rows?**

PublicSurplus only labels the figure "Opening Price" while a lot has no bids; once someone bids it becomes "Current Price". About 18% of open lots are in that state. `currentPrice` is on every row either way.

**Why is `highBidder` empty?**

Nobody has bid yet. `bidCount` will be `0` on the same row.

**Why are `make`, `model` and `vin` empty even with `includeItemSpecs` on?**

Only lots where the agency filled in the vehicle form carry them — around 20% of all lots, and the large majority of Motor Pool and Heavy Equipment ones. Use `onlyWithVin` to keep just those; it works whether or not you bought the block.

**A VIN did not decode.**

NHTSA vPIC answers `200` with an error code for a VIN it cannot read, usually because the agency typed it wrong. The row keeps its `vin` and simply gets no `vin*` columns.

**The run got slower or started failing.**

PublicSurplus is a single application server and this Actor reads five pages at a time on purpose. If your IP gets rate-limited, switch `proxyConfiguration` on.

### FAQ

| Question | Answer |
| --- | --- |
| Do I need a PublicSurplus account or API key? | No. PublicSurplus has no public API. This reads public auction pages only, so there is nothing to register or authenticate. |
| Will I be charged for data I did not ask for? | No. Nine of the twelve events are optional blocks that are off by default; a run left alone bills `result-item`, `listing-page` and `apify-actor-start` and nothing else. |
| Can I filter on a column without paying for it? | Yes. Filters run on the complete row, before the optional blocks are stripped. `onlyWithVin`, `onlyWithBids`, `minBidCount`, `onlyWithImages` and `onlyWithAttachments` all work with every block off. |
| Does it cover closed auctions and final sale prices? | No. PublicSurplus puts closed lots behind a login, answering `401` to anonymous callers. This Actor returns open lots only. |
| Does it cover Canada? | Yes. All 13 Canadian provinces and territories are in the region list, alongside 50 US states, DC and four US territories. |
| Can I fetch one specific lot? | Yes. Put its URL or auction number in `auctionUrls`. |
| Can I follow one agency? | Yes. Put its `agencyId` in `agencyIds` and the Actor walks its whole open catalogue. |
| How current is the data? | Live. Every row is built from the page as it stands, and `timeLeftSeconds` is measured against PublicSurplus's own server clock. |
| How many lots are reachable? | About 5,750 open site-wide, measured 20/08/2026, across roughly 230 pages of 25. A single search reaches all of them. |
| How many rows per run? | Free plan: 50. Paid: up to 10,000, bounded by how many lots match your filters. |
| Does it bid for me? | No. It reads public auction pages and never logs in, bids or transacts. |
| Does it need a proxy? | No. It runs proxy-free by default. |
| Is this an official PublicSurplus product? | No. It is unofficial and reads only publicly available PublicSurplus data. |

### Related actors

- [GovDeals Scraper](https://apify.com/parseforge/govdeals-scraper?fpr=vmoqkp): the other big US government surplus marketplace.
- [Ritchie Bros Auctioneers Scraper](https://apify.com/parseforge/ritchie-bros-auctioneers-scraper?fpr=vmoqkp): heavy equipment and truck auctions worldwide.
- [Government Bids Scraper](https://apify.com/parseforge/governmentbids-scraper?fpr=vmoqkp): public sector solicitations and RFPs.

Browse the full [ParseForge collection](https://apify.com/parseforge?fpr=vmoqkp) for more scrapers.

🆘 **Need help?** Email parseforge@protonmail.com with your run ID, your input, and what you expected.

⚠️ **Disclaimer.** This Actor is unofficial and is not affiliated with, endorsed by, or sponsored by PublicSurplus or The Public Group, LLC. It collects only publicly available auction data — lots that public agencies publish for anyone to browse — and no buyer accounts or personal profiles. `highBidder` is the masked handle PublicSurplus itself displays publicly and cannot be resolved to a person. You are responsible for using the data in compliance with PublicSurplus's terms and applicable laws.

# Actor input Schema

## `keyword` (type: `string`):

Word or phrase to search for. PublicSurplus matches it against the lot title AND the full description, so `truck` also finds a cabinet whose description mentions a hand-truck. Leave empty to take every open auction.

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

Run several keyword searches in one go. Each is a separate search on PublicSurplus and the results are merged, deduplicated by auction id.

## `category` (type: `string`):

One of PublicSurplus's 28 categories or 204 sub-categories. Accepts the name (`motor pool`), a sub-category (`motor pool > truck`, or just `truck`), or the numeric id.

## `categoryIds` (type: `array`):

Extra categories to sweep, by name or numeric id. Each becomes its own search.

## `region` (type: `string`):

Keep only auctions in one region. Accepts the name (`California`), the two-letter code (`CA`), or the site's own `all,ca`. Covers all 68 regions the site lists: 50 US states, DC, four US territories and 13 Canadian provinces and territories.

## `regions` (type: `array`):

Sweep several regions in one run. Each becomes its own search.

## `agencyIds` (type: `array`):

Pull every open auction from specific agencies instead of searching. Use the `agencyId` from any row this Actor returns, which is the `orgid` in the agency's PublicSurplus URL. When set, the keyword, category and region filters are not sent — PublicSurplus has no agency-plus-keyword endpoint.

## `auctionUrls` (type: `array`):

Specific auctions to fetch, skipping discovery entirely. Accepts a full https://www.publicsurplus.com/sms/auction/view?auc=… URL or the bare auction number.

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

Maximum number of auctions written to the dataset. A ceiling, not a target — a narrow filter returns fewer. About 5,700 auctions are open site-wide at any moment.

## `maxPages` (type: `integer`):

How deep discovery may page into one search before moving on. A search page holds 25 rows, an agency page 50. Discovery stops as soon as it has enough candidates, so this is a ceiling. Each page actually read is one listing-page event.

## `priceMin` (type: `integer`):

Keep only lots whose current price is at least this. Sent to PublicSurplus, so cheaper lots are never downloaded.

## `priceMax` (type: `integer`):

Keep only lots whose current price is at most this. Leave both price fields at 0 to see every lot.

## `endingWithinHours` (type: `string`):

Only auctions closing inside this window. PublicSurplus offers these five steps and no others.

## `startedWithinHours` (type: `string`):

Only auctions that opened inside this window — the fastest way to poll for new listings. Mutually exclusive with `Ending within` on the site's own form.

## `zipCode` (type: `string`):

Five-digit US ZIP code at the centre of a radius search. Needs `Miles from ZIP` to do anything.

## `milesFromZip` (type: `string`):

Radius around the ZIP code. PublicSurplus offers these steps and no others.

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

Order PublicSurplus returns the listing in. With several keywords, categories or regions the merged set is sorted again before rows are written.

## `sortDescending` (type: `boolean`):

Flip the sort — highest price first, newest auction id first, Z–A.

## `auctionTypes` (type: `array`):

Keep only these kinds of lot. Leave empty for all three.

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

Drop lots nobody has bid on yet.

## `onlyWithoutBids` (type: `boolean`):

The opposite — keep only untouched lots. Useful for finding bargains that are about to close.

## `minBidCount` (type: `integer`):

Keep lots with at least this many bids.

## `maxBidCount` (type: `integer`):

Keep lots with at most this many bids.

## `onlyWithImages` (type: `boolean`):

Drop lots the agency published without a single photo.

## `onlyWithVin` (type: `boolean`):

Keep only vehicles carrying a valid 17-character VIN — the rows a VIN decode can enrich.

## `onlyWithAttachments` (type: `boolean`):

Keep only lots with an attached document, such as a title, inspection report or site map.

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

Post-filter on the two-letter region code. Cheaper than `More states` when you already have a broad search running, because it does not fan out into separate searches.

## `titleExcludes` (type: `array`):

Drop lots whose title or description contains any of these words. Case-insensitive.

## `listingOnly` (type: `boolean`):

Skip the lot's own page for a faster, shallower run: id, title, price, region, closing time, thumbnail and lot type, straight off the listing. Leave it off to also get the condition grade, the selling agency's name and the start date. This does not change the per-row price, and the page is read anyway if you tick an optional block below or set a filter that needs it.

## `includeBidding` (type: `boolean`):

Bid count, opening price, minimum bid, bid increment, the masked high-bidder handle with their years on the platform, reserve status, buy-it-now price and Dutch quantity.

## `includeAgencyProfile` (type: `boolean`):

The selling agency's numeric id, URL slug, storefront, logo and a link to every other lot it has open. The agency's name is in the base row either way.

## `includePickupLocation` (type: `boolean`):

Location name, street, city, state and ZIP as separate columns, plus the site's map link.

## `includeSaleTerms` (type: `boolean`):

Accepted payment methods, the online card ceiling, the payment caveat, shipping arrangement, bid-deposit rule and the agency's terms document.

## `includeItemSpecs` (type: `boolean`):

The agency's own attribute block as typed columns: vehicle year, make, model, mileage, VIN, engine, transmission, tires and running condition; equipment manufacturer and hours; real-estate parcel number, property type, zoning, lot acres and building square feet. Plus the raw label/value pairs and the CARFAX link. Around 20% of lots carry one.

## `includeVinDecode` (type: `boolean`):

For lots with a 17-character VIN, add 16 NHTSA vPIC columns — body class, fuel type, cylinders, displacement, drive type, GVWR class, assembly plant and more — from the US government's public vehicle database. One extra request per vehicle. Works on its own: you do not need the specs block to get the decoded columns.

## `includeImages` (type: `boolean`):

Every full-resolution photo the agency uploaded, in order, plus the count.

## `includeAttachments` (type: `boolean`):

Documents the agency attached — titles, inspection reports, site maps — as name, URL and file-type triples, plus the count. Around 6% of lots carry one.

## `includeDescription` (type: `boolean`):

The agency's full lot description. Some run to several thousand words.

## `descriptionFormat` (type: `string`):

`text` flattens the agency's markup into readable plain text; `html` keeps it as published. Only matters when the description is switched on.

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

Off by default. PublicSurplus answers plain requests, so a run costs you nothing in proxy traffic. Turn it on only if your runs start getting rate-limited.

## Actor input object example

```json
{
  "keyword": "truck",
  "keywords": [],
  "categoryIds": [],
  "regions": [],
  "agencyIds": [],
  "auctionUrls": [],
  "maxItems": 50,
  "maxPages": 40,
  "sortBy": "timeLeft",
  "sortDescending": false,
  "auctionTypes": [],
  "onlyWithBids": false,
  "onlyWithoutBids": false,
  "onlyWithImages": false,
  "onlyWithVin": false,
  "onlyWithAttachments": false,
  "states": [],
  "titleExcludes": [],
  "listingOnly": false,
  "includeBidding": false,
  "includeAgencyProfile": false,
  "includePickupLocation": false,
  "includeSaleTerms": false,
  "includeItemSpecs": false,
  "includeVinDecode": false,
  "includeImages": false,
  "includeAttachments": false,
  "includeDescription": false,
  "descriptionFormat": "text",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `auctions` (type: `string`):

Every auction row this run saved.

# 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 = {
    "keyword": "truck",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("parseforge/publicsurplus-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 = {
    "keyword": "truck",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("parseforge/publicsurplus-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 '{
  "keyword": "truck",
  "maxItems": 50
}' |
apify call parseforge/publicsurplus-scraper --silent --output-dataset

```

## MCP server setup

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