# PropertyRoom Scraper - Police Auction Lots & Consignors (`scrapersdelight/propertyroom-lot-scraper`) Actor

From $0.90 per 1,000 rows, no start fee. Every open lot on PropertyRoom, the auctioneer for US police and municipal seized property: live bid, bid count, close time, condition, photos, and the consignor's name, street, city, state, ZIP and phone. 16,092 lots counted; 6,618 unreachable by category.

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

## Pricing

$0.90 / 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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## 🚔 PropertyRoom Scraper — Police Auction Lots, Live Bids & Consignors

Scrape every open auction lot on **PropertyRoom.com**, the contracted auctioneer that US police
departments and municipalities use to dispose of unclaimed, seized and surplus property. One row
per lot: the **live bid**, the bid count, the exact close time, the condition the consignor stated,
the full photo set — and the **consignor's published name, street, city, state, ZIP and phone**.

**$0.90 per 1,000 lots. No run-start fee.** You are charged once per row delivered and for nothing
else. A run that matches nothing costs nothing.

Every number on this page was measured on **2026-09-18** by running this Actor through an Apify
residential exit. None of them is an estimate.

***

### 📊 What does PropertyRoom Scraper do?

- 🔎 **Reads the whole catalogue.** 16,092 open lots in PropertyRoom's sitemap; 9,568 reachable
  through its browse pages. Both paths are supported and the difference is explained below.
- 💰 **Gets the current bid** — the number PropertyRoom does not put in the listing HTML at all.
  1,000 of 1,000 rows on a live run carried a price, a bid count and a bid increment.
- 🏢 **Gets the consignor's contact block.** Display name, street, city, state and ZIP on 1,000 of
  1,000 rows; phone on 598. That is a pawn shop, coin dealer or jewellery wholesaler with a real
  address on two thirds of lots — a lead list hiding inside an auction site.
- 🚔 **Flags law-enforcement consignment** using PropertyRoom's own sentence, not a guess.
  240 of 1,000 rows on a live run.
- ⏱️ **Sorts by closing-soonest, cheapest, or most-contested** before spending anything, so your
  row cap lands on the lots you actually want.
- 🧾 **Filters are free.** Status, auction format, price band, bid count, closing window and title
  keyword all run against PropertyRoom's batched bid feed *before* the per-lot fetch. A lot your
  filter rejects is never delivered and never charged.
- 🖼️ **Every photo**, as a direct CDN URL. 1,000 of 1,000 rows.
- 🏛️ **PropertyRoom's own client roster** — the 45 law-enforcement and municipal agencies it names
  publicly, written to a separate `CLIENT_AGENCIES` record. Site-wide reference, never faked onto
  a row.
- 📋 **A RUN\_SUMMARY** that reconciles what was walked against what was delivered and charged.

***

### 👤 Who uses a PropertyRoom scraper?

- **Jewellery, watch and coin dealers** — the site's largest department is jewellery (3,762 lots),
  then collectibles and coins. Filter to no-bids-yet lots closing in the next hour.
- **Electronics and tool resellers** on eBay or Amazon — 877 electronics lots, 729 tools, with
  stated condition and the model number in the description.
- **Pawnbrokers** — scan bulk lots for inventory instead of clicking through 383 pages.
- **Asset-recovery and insurance-salvage desks** — track recovered property as it moves through
  the law-enforcement disposal channel.
- **Price researchers** — closed lots carry the realised price, so you can compare what things
  actually sell for against what is currently being asked.
- **B2B lead builders** — 27 distinct consignor accounts across a 1,000-lot run, each with a
  physical address; 325 of those rows shipped from Florida, 195 from California.

***

### ⚙️ How it works

PropertyRoom is an ASP.NET site whose listing pages do **not** contain the current bid. The bid
lives in the page's own XHR. This Actor uses that endpoint directly, which is both more complete
and about three times smaller than the rendered page:

1. **Enumerate.** Either the browse catalogue (`/c/<category>/<page>`, 25 lot cards a page, in the
   site's own closing-soonest order) or the sitemap (every lot URL in one request).
2. **Hydrate the live state in batches.** PropertyRoom's `GetClientListings` endpoint accepts an
   **array** of lot IDs and answers them all in one request — verified to 1,000 IDs in 2.4 seconds.
   Live bid state for the entire 16,092-lot catalogue costs 33 requests, not 16,092.
3. **Filter and sort here**, on data that cost about 480 bytes a lot.
4. **Fetch the full record** only for the lots that survived and are about to be delivered.

That ordering is why the price is what it is. We charge per row **delivered** but pay per lot
**examined**, so any filter applied after the expensive step would put the cost of your rejected
lots inside the price of your kept ones.

#### The two enumeration paths, measured

| | Browse catalogue (default) | Full sitemap census |
|---|---|---|
| Lots reachable | **9,568** (383 pages, walked exhaustively) | **16,092** |
| Lots only this path reaches | — | **6,524** |
| Ordered by the site? | Yes, closing soonest | No |
| Platform usage, 25-row run | **$0.0012** | $0.0389 |
| Fixed cost before the first row | ~$0.0000 | ~$0.047 |
| Pays for itself from | any size | about 100 rows |
| Best for | everyday runs, one department, capped runs | completeness, big pulls, site-wide price/bid sorts |

The union of **every** browsable category page is 9,550 lots — so this is not a quirk of the
all-items page. A third of PropertyRoom's open inventory simply is not linked from any category.
Set `source` to `sitemap` when you need it. The extra cost is one 4.6 MB read plus a live-state
sweep of all 16,092 lots — about $0.047 of Apify usage before the first row, regardless of how many
rows you keep — so it amortises away on a large pull and not on a small one. A sitemap run under
100 rows says so in `RUN_SUMMARY.warnings`.

***

### 💵 Pricing

| | |
|---|---|
| Model | Pay per event — one event, `lot-scraped` |
| Price | **$0.0009 per lot delivered** ($0.90 per 1,000) |
| Run-start fee | **None** |
| Charged for filtered-out lots | No — they are dropped before any per-lot request |
| Charged for IDs that do not exist | No |
| Charged for lots we could not reach | No |
| Charged for a run that matches nothing | No |

Apify platform usage is separate and billed by Apify. Measured on this Actor at 1024 MB through
Apify Proxy (residential, US), across three runs of 25 / 250 / 1,000 rows: **$0.00021 per row**,
on a fixed base of about **$0.0015 per run**. The slope was within 3% across all three run sizes.
83% of that is residential proxy traffic — note that Apify meters residential transfer on the
**decompressed** payload, so the gzip this Actor requests on every call buys speed, not proxy
dollars.

***

### 📥 Input

```json
{
  "source": "catalogue",
  "categories": ["coins"],
  "listingStatus": "open",
  "bidState": "noBidsYet",
  "maxCurrentPrice": 50,
  "closingWithinHours": 6,
  "sortBy": "endingSoonest",
  "maxLots": 25,
  "fetchDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "US"
  }
}
```

| Field | Type | Default | What it does |
|---|---|---|---|
| `source` | string | `catalogue` | `catalogue` walks browse pages (9,568 lots, cheap, pre-ordered). `sitemap` reads the full census (16,092 lots). |
| `categories` | array | all items | Category slugs, OR-ed. Measured lot counts: jewelry 3,762 · everything-else 1,245 · collectibles 1,222 · electronics 877 · coins 746 · tools-and-equipment 729 · fashion 635 · watches 532 · fine-art 213 · sunglasses-and-eyewear 161 · musical-instruments 149 · computers 120 · vehicles 73 · bikes 18 · steal-of-the-day 11. Sub-category slugs work too. Ignored in sitemap mode. |
| `lotIds` | array | — | Re-check known lots and skip enumeration entirely. Overrides `categories`. |
| `listingStatus` | string | `any` | `open` · `scheduled` · `closed`. Measured on 1,000 random lots: 580 open, 418 scheduled, 2 closed. |
| `auctionFormats` | array | all | `Auction` (753 of 1,000) · `Fixed Price` (233) · `Reserve Auction` (11) · `Bid to be Approved Auction` (3). |
| `bidState` | string | `any` | `noBidsYet` · `hasBids`. 58 of 1,000 random lots had a bid. |
| `minCurrentPrice` / `maxCurrentPrice` | integer | — | Price band. Median price across 1,000 lots was $32; the highest was $3,999. |
| `maxBidCount` | integer | — | Skip lots that already have a bidding war. |
| `closingWithinHours` | integer | — | The "what can I still win today" filter. |
| `titleContains` | array | — | Keywords, OR-ed, case-insensitive. |
| `sortBy` | string | `endingSoonest` | `endingSoonest` · `endingLatest` · `priceLowest` · `priceHighest` · `mostBids` · `newestListed` · `asListed`. |
| `maxLots` | integer | 50 | Hard cap on rows delivered **and** charged. |
| `fetchDetails` | boolean | `true` | Off skips the per-lot record: about five times faster, but 33 fields come back null at the same price. |
| `includeClientAgencies` | boolean | `true` | Writes the `CLIENT_AGENCIES` record (45 named agencies, the site's 4,300+ client claim, and 5 clients that publish their own auction page). Two extra page reads. |
| `concurrency` | integer | 8 | 200 detail calls at 8 took 25.6 s with zero errors. |
| `proxyConfiguration` | object | Apify RESIDENTIAL, US | See **Proxy** below. This one matters. |

***

### 📤 Output — a real row from a live run

Not an example. This is row 1 of a 1,000-row run on 2026-09-18, trimmed only where marked.

```json
{
  "lotId": "18940685",
  "lotUrl": "https://www.propertyroom.com/l/nikon-coolpix-l830-digital-camera/18940685",
  "title": "Nikon CoolPix L830 Digital Camera",
  "category": "Digital & SLR Cameras",
  "categoryId": 1057,
  "auctionFormat": "Auction",
  "listingStatus": "open",
  "isOpen": true,
  "currentPriceUsd": 32,
  "soldPriceUsd": null,
  "startingBidUsd": 1,
  "minimumBidUsd": 35,
  "bidIncrementUsd": 3,
  "reservePriceUsd": 0,
  "buyersPremiumUsd": 0,
  "bidCount": 13,
  "hasBids": true,
  "activeBidders": 7,
  "watchCount": 10,
  "highBidderLocation": "Walker, LA",
  "highBidderMemberSince": "August 2026",
  "closesAt": "2026-09-18T22:12:00.000Z",
  "closesAtLocalEastern": "Sep 18, 2026 at 06:12 PM",
  "secondsRemaining": 140,
  "description": "Get a great deal with this online auction presented by PropertyRoom.com on behalf of a law enforcement or public agency client.\nModel: L830 Memory Card: Not included Battery: \"AA\", not included Accessories: Carry bag only ...",
  "condition": "Fair, scuffed and scratched",
  "itemNumber": "1320-002549",
  "itemLocation": "CA,917611547",
  "quantityOffered": 1,
  "quantityRemaining": 1,
  "weight": 4,
  "weightUnit": "pounds",
  "shippingEstimateUsd": 16.95,
  "shippingService": "Standard",
  "shippingCarrier": "FlatRate",
  "freeShipping": false,
  "allowedCountries": "US",
  "sellerId": 1,
  "sellerName": "Ontario, California",
  "sellerStreet": "3959 E. Guasti Road Unit D",
  "sellerCity": "Ontario",
  "sellerState": "CA",
  "sellerZip": "91761",
  "sellerPhone": null,
  "sellerIsPropertyRoom": true,
  "isLawEnforcementConsignment": true,
  "consignorAccountCode": "1320",
  "photoCount": 3,
  "titlePhotoUrl": "https://content.propertyroom.com/listings/sellers/seller1/images/origimgs/nikon-coolpix-l830-digital-camera-1_10920261944283282256.jpg",
  "photoUrls": ["... 3 direct CDN URLs ..."],
  "scrapedAt": "2026-09-18T22:10:34.817Z"
}
```

#### Measured field fill — 1,000 rows, one live run, 2026-09-18

**100% filled (1,000 / 1,000):** `lotId` · `lotUrl` · `title` · `category` · `categoryId` ·
`auctionFormat` · `listingStatus` · `isOpen` · `currentPriceUsd` · `startingBidUsd` ·
`minimumBidUsd` · `bidIncrementUsd` · `reservePriceUsd` · `buyersPremiumUsd` · `bidCount` ·
`hasBids` · `activeBidders` · `watchCount` · `closesAt` · `closesAtLocalEastern` ·
`secondsRemaining` · `description` · `quantityOffered` · `quantityRemaining` ·
`shippingEstimateUsd` · `shippingService` · `shippingCarrier` · `freeShipping` ·
`allowedCountries` · `sellerId` · `sellerName` · `sellerStreet` · `sellerCity` · `sellerState` ·
`sellerZip` · `sellerIsPropertyRoom` · `isLawEnforcementConsignment` · `photoCount` ·
`titlePhotoUrl` · `photoUrls` · `scrapedAt`

| Field | Fill | Why it is not 100% |
|---|---|---|
| `itemNumber` | 999 / 1,000 | One lot had none. |
| `consignorAccountCode` | 859 / 1,000 | Not every SKU starts with PropertyRoom's four-digit account prefix. |
| `sellerPhone` | 598 / 1,000 | Some consignors publish no phone. Blank, never invented. |
| `weight` / `weightUnit` | 593 / 1,000 | PropertyRoom writes `0` for "not recorded"; a 0 lb parcel is not a fact, so it is `null` and the unit goes with it. |
| `condition` | 464 / 1,000 | Condition is free text inside the description, not a field. Parsed where stated, `null` where not. |
| `itemLocation` | 407 / 1,000 | Published mainly on PropertyRoom's own lots. |
| `highBidderLocation` / `highBidderMemberSince` | 232 / 1,000 | Only exists once a lot has a bid: **232 of the 236 rows that had bids** carried it. |
| `soldPriceUsd` | 0 / 1,000 | This run was filtered to live lots. It fills on a closed lot that actually received a bid — verified separately against closed lots 18939117, 18947386 and 18955837. |

On a 200-lot random sample the picture is the same, with two extra findings: `sellerState` and
`sellerZip` were 197/200 rather than 1,000/1,000, because PropertyRoom's "In-Place Auctions" lots
publish `City: "See Auction Listing"` and a whitespace state; and `condition` parsed on 101/200.

***

### 🚔 What this Actor does **not** claim

This is the part other listings get wrong, so it is stated plainly.

- **The contracting police department or agency is NOT published on a PropertyRoom listing.**
  Not in the HTML, not in the JSON, not in the images. We went looking properly before saying so:
  all 16 `/ajax/ajax.svc/` methods were enumerated out of the site's own script bundles and there
  is no `GetSeller`, `GetMerchant` or `GetAgency`; `/seller/<id>` answers an error page and
  `/m/<id>` is empty, because the `/m/` route is keyed on a merchant display name rather than the
  numeric ID; **0 of 60** lots pulled from Vehicles and Police & Fire Surplus — where a municipal
  consignment is most likely to be named — mentioned an agency; and **3 of 1,000** rows on a live
  run contained an agency-shaped token, all three incidental product copy. What you get instead is:
  - `isLawEnforcementConsignment` — PropertyRoom's own sentence, printed verbatim at the top of the
    description: *"presented by PropertyRoom.com on behalf of a law enforcement or public agency
    client."* 240 of 1,000 rows on a live run; 37 of 200 on a random sample.
  - `sellerIsPropertyRoom` — the lot is sold from PropertyRoom's own account (`sellerId` 1), which
    is the channel its public-agency clients are disposed through. The two agree on **197 of 200**
    random lots, and the `sellerName` on these is the PropertyRoom warehouse handling the lot
    ("Ontario, California", "Frederick, MD", "Ronkonkoma, New York"), not the agency.
  - `consignorAccountCode` — the four-digit prefix of `itemNumber`. It behaves exactly like a
    consignor account: 18 of the 19 third-party merchants in a 200-lot sample used **one** code for
    all their lots, while PropertyRoom's own account used **32 distinct codes across 40 lots** —
    one per client batch. It groups lots from the same agency consignment. It is a code, not a name.
- **The agency names PropertyRoom does publish are site-wide, and we keep them separate.** Its
  `/about-us/partners` page names **45** law-enforcement and municipal clients — Atlanta PD,
  NYPD, Chicago PD, Cook County Sheriff's Office, Washington State Patrol and so on — against the
  **4,300+** clients it claims in total, and `/client/linking` names five more that publish their
  own auction page. This Actor writes that roster to a **`CLIENT_AGENCIES`** key-value record, and
  deliberately **does not** join it onto any row: nothing links a lot to one of those names, so a
  per-row agency column would be an invention. Turn it off with `includeClientAgencies: false`.
- **PropertyRoom's own "police items" view does not filter.** `cat.aspx?policeitems=true` returns
  the same 383-page result set as the unfiltered catalogue, lot for lot, measured 2026-09-18. Any
  police flag built on it marks the entire site. Ours is built on the site's own sentence instead.
- **There is no site search.** `robots.txt` disallows `/searchresult.aspx`, so `titleContains`
  filters the catalogue rather than querying PropertyRoom's search.
- **No sub-category column.** PropertyRoom's listing record exposes the leaf category only. We
  checked whether the browse path could supply a parent: on 99 category pages, the first lot's own
  category disagreed with the page's breadcrumb **55 times**, because category pages mix in
  descendant categories. A derived "subcategory" column would have been wrong more often than right,
  so it is not shipped.
- **A handful of scheduled lots carry placeholder close dates** — 9 of 1,000 sat in 2050, 2201 or
  3025\. That is PropertyRoom's own value and it is passed through unchanged rather than nulled.
- **`listingStatus` and `secondsRemaining` can disagree for under a minute** at the moment a lot
  closes: the countdown goes negative before PropertyRoom flips the status code. Both are the
  site's own values; `isOpen` requires both to agree.
- **`shippingEstimateUsd` of 0** means free shipping or local pickup, not missing data — 26 of the
  29 zero-shipping lots in a 200-lot sample were flagged free shipping.
- **Closed lots are not browsable at all.** PropertyRoom drops a lot from its category pages the
  second it closes. `listingStatus: "closed"` therefore switches to the sitemap by itself and says
  so in `RUN_SUMMARY.warnings`. Because the sitemap only carries a closed lot for a short while,
  this is a recent-results view rather than a sold-price archive.
- **A narrow filter stops the walk, on purpose, and says so.** Browse pages carry 25 lots each and
  PropertyRoom has no page-size parameter (seven variants probed, all returned 25), so a filter
  that rejects most of the catalogue makes the walk read a lot of pages to find a few rows. Two
  bounds apply, and both are reported in `RUN_SUMMARY.warnings` when they fire: the walk abandons a
  category after **40 consecutive pages (1,000 lot cards) that match nothing**, and it stops
  altogether at **0.8 browse pages per row you asked for** — the point where page traffic would
  cost more than the rows it finds are worth. An unfiltered run never comes close: it stops after
  `maxLots / 25` pages. If you want a wide filter over the whole site, set `source` to `sitemap`,
  which reads every lot ID in one request instead of 25 at a time.

***

### 🌐 Proxy — read this one

PropertyRoom sits behind **CloudFront**, and it rejects Apify's shared datacenter exits.

| Pool | Result |
|---|---|
| Apify **RESIDENTIAL**, country US | HTTP 200 on 20 of 20, and on 1,000+ requests across every run since. **Zero blocks.** |
| Apify shared **datacenter** | HTTP 403 with a CloudFront block page on 3 of 9 requests. A full run on this pool aborted on its first page. |

Residential + US is the schema default, so you get it automatically. If you clear it, the run fails
fast with a message naming the cause and the fix rather than returning an empty dataset. The Actor
also treats a **200 that carries a CloudFront block page** as a transport failure, not an empty
category — that distinction is what stops a blocked run from looking like a small marketplace.

***

### ❓ FAQ

**Does it get the current bid?**
Yes, on every row. PropertyRoom's listing HTML does not contain it — the bid comes from the page's
own `GetClientListings` endpoint, which also supplies the bid count, bid increment, close time and
status. 1,000 of 1,000 rows on a live run.

**Can I get the realised price on lots that have finished?**
Yes, for recently closed lots. Set `listingStatus` to `closed` and the run switches to the sitemap
automatically, because PropertyRoom removes a lot from its browse pages the moment it closes — a
catalogue walk would read all 384 pages and find nothing (measured: 587 seconds, 0 rows, before
this was fixed). Closed lots linger in the sitemap for a while afterwards, so this is a
recent-results view, not a historical archive. `soldPriceUsd` fills when the lot actually received
a bid; a closed lot with zero bids reports its opening ask as `currentPriceUsd` and
`soldPriceUsd: null`, because nobody bought it.

**Does it tell me which police department the item came from?**
No, and no scraper can: PropertyRoom does not publish it on a listing. See *What this Actor does
not claim* above for what was probed and the three fields you get instead. What you do get is
`CLIENT_AGENCIES` — the 45 agencies PropertyRoom names on its own partners page — as a separate
reference record, never joined onto a row.

**A competing listing advertises a "seller agency" field. What is that?**
It is the consignor's pickup-facility name, which that Actor's own README defines as exactly that
and whose own example value is "GC Pawn" — a pawn shop. It is the same field this Actor ships as
`sellerName`, filled on 1,000 of 1,000 rows. Its companion "seller state" is derived from the
facility's ZIP; we read the state straight from the source record instead.

**How many lots are on the site?**
16,092 open lot URLs in the sitemap on 2026-09-18, of which 99.8% of a random 1,000 were still
live. The browsable catalogue reaches 9,568 of them.

**Why is the default only the browse catalogue if the sitemap has more?**
Because the browse pages are already in closing-soonest order, so a capped run stops after a few
pages: a 25-row run costs $0.0012 of platform usage there against $0.0389 through the sitemap.
Switch `source` to `sitemap` the moment you need the 6,524 lots the catalogue omits.

**Am I charged for lots my filters reject?**
No. Filters run against a batched feed costing about 480 bytes a lot, before any per-lot request.

**Is there a run-start fee?**
No. If nothing matches, the run costs you nothing.

**What happens if a lot ID does not exist?**
It is recorded in `RUN_SUMMARY.notFound` and charged nothing. IDs that answer but publish no record
go in `noRecordPublished`; lots we could not reach after retries go in `unreachable`. The three are
kept apart on purpose — they mean different things to a buyer working a list.

**How fresh is the bid?**
It is read at run time, seconds before the row is written. `scrapedAt` records the moment, and
`secondsRemaining` is the countdown at that moment.

**Can I monitor a watchlist cheaply?**
Yes — pass `lotIds`. That skips enumeration entirely and costs one batched request plus one detail
call per lot.

**Can I turn the detail fetch off?**
Yes, `fetchDetails: false`. It is about five times faster, but 33 fields come back null and the
price per row is the same, so it only makes sense for a pure price sweep over lots you have already
profiled.

**Does it work without a proxy?**
No. CloudFront blocks the platform IP and the shared datacenter pool. Use Apify Proxy on
RESIDENTIAL with country US — which is what the input schema already fills in.

**What is `consignorAccountCode` for?**
Grouping. On PropertyRoom's own lots it separates one agency consignment from another, so you can
tell a 40-lot police batch apart from the rest of the day's inventory even though neither is named.

***

### ⚖️ Source and legal

- **Source:** public listing pages on `propertyroom.com`, its public XML sitemap, and the public
  JSON endpoints those pages call (`/ajax/ajax.svc/GetClientListings`, `/ajax/ajax.svc/GetListing`).
  No login, no account, no credentials. Only data PropertyRoom serves to any visitor.
- **`robots.txt`**, quoted verbatim as of 2026-09-18:
  ```
  User-agent: *
  Disallow: /my-items.aspx?*
  Disallow: /error.aspx?*
  Disallow: /watchlist.aspx?*
  Disallow: /catalognew.aspx?*
  Disallow: /userlogin.aspx?*
  Disallow: /bidhistory.aspx?*
  Disallow: /searchresult.aspx?*
  Disallow: /account/
  Disallow: /watchlist/
  Disallow: /activity/
  ```
  This Actor reads the sitemap, `/c/` category pages and `/ajax/ajax.svc/` endpoints. None of those
  paths appears in that list, and there is no `Crawl-delay` directive.
- **Why this data is public in the first place:** police and municipal unclaimed or seized property
  must generally be disposed of by public auction under state statute — for example New York
  Personal Property Law §253, California Civil Code §2080.6 and Florida Statutes §705.103.
  PropertyRoom is the contracted auctioneer for many of those agencies. The listing, its price and
  its consignor block are the public face of a statutory sale.
- **Contact data:** the consignor block is business contact information a seller publishes on its
  own listings. `highBidderLocation` is the city and state PropertyRoom itself displays next to a
  bid, with no name attached. No personal identifiers are collected.
- **You are responsible** for how you use the output, including any applicable terms of service and
  data-protection law in your jurisdiction.

***

*Built and measured on 2026-09-18. Every count, fill percentage and cost figure on this page came
from running this Actor, not from an estimate.*

# Actor input Schema

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

CATALOGUE walks /c/ browse pages, which the site publishes in closing-soonest order. A capped run in that order stops after a few pages, so it is much cheaper — measured $0.0012 of platform usage for 25 rows against $0.0389 through the sitemap, because the sitemap path must read a 4.6 MB document plus the live state of all 16,092 lots before it can sort them. That is roughly $0.047 of fixed cost before the first row, so the sitemap only pays for itself above about 100 rows; below that the run says so in RUN\_SUMMARY. Use SITEMAP when you need the 6,524 lots the browse pages omit, when you are pulling thousands of rows anyway, or when you are sorting by price or bid count across the whole site. Categories are ignored in sitemap mode — the sitemap does not say which category a lot sits in.

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

PropertyRoom category slugs, OR-ed. Top level with the lot count measured on 2026-09-18: jewelry (3,762) · everything-else (1,245) · collectibles (1,222) · electronics (877) · coins (746) · tools-and-equipment (729) · fashion (635) · watches (532) · fine-art (213) · sunglasses-and-eyewear (161) · musical-instruments (149) · computers (120) · vehicles (73) · bikes (18) · steal-of-the-day (11). Sub-category slugs work too — take them from any /c/ URL, e.g. "jewelry\_diamond-jewelry" or "coins\_precious-metal\_silver". Leave empty to walk "propertyroom", the site's own all-items catalogue (9,568 lots). Ignored when Source is set to the sitemap.

## `lotIds` (type: `array`):

Re-check known lots and skip enumeration entirely — the cheapest way to watch a shortlist of prices. The lot ID is the number at the end of a listing URL, e.g. "18955621" from https://www.propertyroom.com/l/some-slug/18955621. Overrides Categories.

## `listingStatus` (type: `string`):

PropertyRoom's own status code. OPEN lots take bids now. SCHEDULED lots are published with a price and a close date but no bid has ever landed on one in our sample — the soonest opened 25 hours out. CLOSED lots are past their close time; for those, soldPriceUsd is filled when the lot actually received a bid.

## `auctionFormats` (type: `array`):

Keep only these formats. Measured on a random 1,000: Auction 753, Fixed Price 233, Reserve Auction 11, Bid to be Approved 3. Leave empty for all four.

## `bidState` (type: `string`):

Measured on a random 1,000 lots: 58 carried at least one bid. Note that a lot with no bids still shows a price — that is its opening ask, not a bid, and bidCount tells the two apart.

## `minCurrentPrice` (type: `integer`):

Keep only lots at or above this price. Measured spread across 1,000 open lots: median $32, maximum $3,999.

## `maxCurrentPrice` (type: `integer`):

Keep only lots at or below this — the bargain filter. Combine with "No bids yet" to find lots nobody has touched.

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

Skip lots that already have a bidding war on them. 0 is the same as "No bids yet".

## `closingWithinHours` (type: `integer`):

Keep only lots whose countdown is under this many hours — the "what can I still win today" filter. Read from PropertyRoom's own countdown, so it costs nothing to apply.

## `titleContains` (type: `array`):

Keep only lots whose title contains one of these words (case-insensitive), e.g. \["rolex", "14k", "macbook"]. Multiple entries are OR-ed. Matched against the listing title on category pages and against the URL slug in sitemap mode — both of which PropertyRoom derives from the same title. PropertyRoom's own search page is robots-disallowed, so this is a filter over the catalogue rather than a site search.

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

Ending-soonest means soonest among lots that are still OPEN — a lot that has already closed has a negative countdown, and sorting on that naively would fill your first page with yesterday's dead lots. Closed lots follow, most recently closed first. Ending-soonest and As-listed match the site's own browse order, so in catalogue mode they let the walk stop after a few pages; every other sort has to read the whole category before it can order it, which costs more.

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

Hard cap on rows delivered AND charged. You are charged once per delivered row and nothing else, so a small cap is genuinely cheap. The whole open inventory was 16,092 lots on 2026-09-18, so 20000 covers everything.

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

ON (the default): one extra request per delivered lot buys the description, condition, leaf category, photo gallery, quantities, shipping, reserve, watch count, bidder location — and the consignor's name, street, city, state, ZIP and phone. OFF: about five times faster and lighter, but 33 fields come back null at the same price per row. Only worth turning off for a pure price sweep over lots you have already profiled.

## `includeClientAgencies` (type: `boolean`):

Writes a CLIENT\_AGENCIES record to the key-value store holding the law-enforcement and municipal clients PropertyRoom names on its own /about-us/partners page — 45 named agencies against the 4,300+ clients the site claims — plus the five that publish their own auction page. IMPORTANT: this is a SITE-WIDE list and is NOT joinable to a lot. A PropertyRoom listing never names its consigning agency and nothing public resolves the numeric seller id or the consignor account code to one, so no row carries an agency name. Costs two extra page reads per run.

## `concurrency` (type: `integer`):

Requests in flight. Measured 200 detail calls at 8 in 25.6 seconds with zero errors and zero 403s over 1,000+ requests through a residential exit.

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

PropertyRoom sits behind CloudFront. Measured 2026-09-18: Apify's shared DATACENTER pool returned HTTP 403 with a CloudFront block page on 3 of 9 requests, while RESIDENTIAL with country US returned HTTP 200 on 20 of 20 and on every one of 1,000+ requests since. RESIDENTIAL + US is therefore the default. The Actor asks for gzip on every request, so a detail call costs 4.8 KB of residential traffic rather than 14.9 KB.

## Actor input object example

```json
{
  "source": "catalogue",
  "categories": [
    "coins"
  ],
  "lotIds": [],
  "listingStatus": "any",
  "auctionFormats": [],
  "bidState": "any",
  "titleContains": [],
  "sortBy": "endingSoonest",
  "maxLots": 25,
  "fetchDetails": true,
  "includeClientAgencies": true,
  "concurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

One row per PropertyRoom auction lot: live price, bid and watcher counts, exact UTC close time, opening bid, reserve, buyer's premium, shipping, condition, full description, photo gallery, and the consignor's published name, street, city, state, ZIP and phone — plus a flag for whether PropertyRoom is selling the lot on behalf of a law-enforcement or public-agency client.

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

RUN\_SUMMARY: how the catalogue was enumerated and how much of it was covered, how many lots passed your filters, rows delivered (== rows charged), measured field-fill for this run's own rows, and three separate lists for the three kinds of nothing — listing ids that do not exist, pages that answer but publish no record, and lots that could not be reached. None of the three is charged.

## `clientAgencies` (type: `string`):

CLIENT\_AGENCIES: the law-enforcement and municipal clients PropertyRoom names on its own partners page, plus the clients that publish their own auction page. Site-wide reference only — a listing never names its consigning agency, so this is deliberately NOT joined onto any row.

# 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 = {
    "source": "catalogue",
    "categories": [
        "coins"
    ],
    "sortBy": "endingSoonest",
    "maxLots": 25,
    "fetchDetails": true,
    "includeClientAgencies": true,
    "concurrency": 8,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/propertyroom-lot-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "source": "catalogue",
    "categories": ["coins"],
    "sortBy": "endingSoonest",
    "maxLots": 25,
    "fetchDetails": True,
    "includeClientAgencies": True,
    "concurrency": 8,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/propertyroom-lot-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "source": "catalogue",
  "categories": [
    "coins"
  ],
  "sortBy": "endingSoonest",
  "maxLots": 25,
  "fetchDetails": true,
  "includeClientAgencies": true,
  "concurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call scrapersdelight/propertyroom-lot-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/propertyroom-lot-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/vqkRbSfLhXKQJopfc/builds/38mpPYTaO5z6B7gaj/openapi.json
