# Copart Scraper - Salvage Lots, Damage, Title & Bids (`scrapersdelight/copart-lot-scraper`) Actor

From $0.60 per 1,000 rows, no start fee. Every Copart lot: year, make, model, trim, masked VIN, primary and secondary damage, title group and state, odometer, run-and-drive, keys, current bid, buy-it-now, est. retail value, repair cost, sale date, consigning insurer and yard. 417,320 counted.

- **URL**: https://apify.com/scrapersdelight/copart-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.60 / 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

## 🚗 Copart Scraper — Salvage Lots, Damage, Title & Bids

**Every vehicle lot listed on [Copart](https://www.copart.com), with the salvage disclosure a buyer
actually needs.** Year, make, model, trim, the masked VIN, primary and secondary damage, title group
and title state, odometer with its brand, run-and-drive certification, keys, the current bid,
buy-it-now price, Copart's own estimated retail value and estimated cost of repair, the sale date,
and the yard the car is physically sitting in — name, number, city, state, ZIP and coordinates.

**$0.60 per 1,000 rows. No start fee.** You are charged for rows delivered, and for nothing else.

> ### 🏷️ **Which carrier is liquidating the car** — and the cheapest way to get it
>
> On **18.2%** of lots Copart names the **consigning company**, and this Actor returns it in
> `sellerName`. Measured over 1,600 rows: **GEICO** (152), **USAA** (68), **Bristol West** (18),
> **CSAA** (15), **Farmers** (14), **AIG**, **National Powersport Auctions**.
>
> That turns a salvage-price feed into something that also answers *who is writing these off*:
> track one carrier's total-loss volume by damage type, model and yard; spot which insurer is
> dumping a model line this month; size a carrier's salvage flow into a region before you bid.
>
> **Two of the 26 other Copart Actors also return it** — `scrapers_lat/copart-scraper` at
> **$0.008/row** and `bovi/copart-lot-scraper` at **$0.0233/row**, checked in their own latest
> builds' READMEs. This Actor returns the same field at **$0.0006/row**: **13× and 39× cheaper.**
>
> **The honest half:** the other **81.8%** are consigned anonymously and `sellerDisclosed` is
> `false`. It is a **company name on a vehicle record** — there is no person, email, phone or
> address behind it here or on any public Copart surface. Filter on `sellerName` being present when
> you need the carrier; treat the 18.2% as the real coverage, not a rounding error.

***

### 📊 What this Actor does

Copart publishes **417,320 lots** in its public index (counted on 2026-09-18 from Copart's own
`totalElements`, not estimated). This Actor reads them through Copart's own search API — the same
endpoint copart.com's front end calls — and returns one clean, typed row per lot.

- 🚙 **Vehicle identity** — year, make, model, model group, trim, engine, cylinders, fuel,
  transmission, drive, colour, Copart's vehicle type
- 🔑 **Salvage disclosure** — primary damage, secondary damage, Copart's damage code, title group,
  title description, title state, title doc type, odometer + odometer brand, run-and-drive and
  engine-start certification, keys
- 🏷️ **The consigning carrier** — `sellerName` on the 18.2% of lots Copart discloses (GEICO, USAA,
  Bristol West, CSAA, Farmers, AIG). Only 2 of the 26 other Copart Actors return it, at 13× and 39×
  this price.
- 💰 **The money** — current bid, whether any bid exists, buy-it-now price, Copart's estimated
  retail value, estimated value, estimated cost of repair, currency, sale type
- 📅 **The sale** — sale date, sale time, time zone, auction name, lot status
- 📍 **The yard** — yard name, yard number, city, state, country, ZIP, latitude, longitude
- 🧾 **Provenance on every row** — which partition, which query and which page produced it
- 🆓 **Copart's whole filter vocabulary**, written to the key-value store free of charge on every run

#### Who buys this

Auto dismantlers and recyclers sourcing donor vehicles by make/model/year/yard · salvage rebuilders
screening lots on damage code, title group and the bid-to-retail ratio · used-car exporters building
shipping lists by yard and state · VIN-history and valuation vendors · Copart brokers reselling
access to non-licensed buyers.

***

### ⚖️ What this is NOT — read this before you buy

**This is a salvage-lot DATA product. It is not a contact or lead product, and it is not sold as
one.** On 18.2% of lots (291 of 1,600 measured) Copart names the **consigning company** — GEICO,
USAA, Bristol West, CSAA, Farmers, AIG — and that name ships in `sellerName`. Those are the
**insurers writing the vehicle off**: corporate entities attached to a vehicle record. The other
81.8% are consigned anonymously. Either way there is **no person, no email, no phone number and no
address** anywhere on this surface, and the location on every lot is Copart's own yard rather than
a third party's premises. Nothing here is a lead list.

**Why the data is public at all.** Salvage and junk vehicle designations are a *disclosure regime*.
State salvage-title statutes, and federal NMVTIS (49 U.S.C. §30502, implemented at 28 CFR Part 25),
require salvage/junk status to be reported and disclosed to buyers — which is why Copart publishes
title type, damage and odometer against every lot in the first place. That is a **disclosure
obligation about vehicles**, not a contact register about people, and this Actor stays on the
vehicle side of that line.

***

### 🎯 Honest limits (measured, not hedged)

| Limit | The measurement |
|---|---|
| **VIN is masked** | Copart returns the first **11 of 17** characters to anonymous callers — `KNDJT2A64C7******`. Measured: **1,560 of 1,560** standard 17-character VINs masked, always the last 6, always exactly 11 visible. Every row carries `vinMasked` and `vinVisibleChars`, so it can never be mistaken for a full VIN. **No surface reachable without a Copart membership returns the full VIN, and this Actor does not claim otherwise.** |
| **Not every lot has a VIN at all** | 40 of 1,600 rows (2.5%) carry a shorter manufacturer **serial** instead — forklifts and industrial equipment mostly, plus a few boats, jet skis and trailers. Those come back **unmasked**, and `vinMasked: false` says so rather than dressing them up as masked VINs. |
| **Seller is named on a minority of lots** | `sellerName` is present on 18.2% of rows and is the consigning **insurer**, never a person. See the section above. |
| **No sold prices** | Copart does not publish realized/hammer prices to anonymous callers. Every lot in the public index is an **open** lot (`lotStatus: "O"` on 1,600 of 1,600). `currentBid` is the live high bid at the moment of the request; there is no sold-price field and none is implied. |
| **Only 34.3% of lots have a sale date** | 143,154 of 417,320 lots have an auction date in the future. The rest are listed but not yet scheduled. The default scope is the scheduled ones — see the FAQ, because this one has a sharp edge. |
| **No body style** | Copart lets you *filter* on body style but does not *return* it in the search record. It is cut rather than shipped as a column of nulls — use `rawFilters` with `body_style:"4DR SEDAN"` to filter on it. |
| **No grid/row position** | Copart's "Grid/Row" yard-position column came back as the empty string on 1,600 of 1,600 records. Cut, not shipped. |
| **One thumbnail, not a gallery** | Full photo galleries need a per-lot request this Actor does not make. `thumbnailUrl` is the one image the search record carries (99.9% filled). |
| **Copart is Imperva-walled** | copart.com intermittently serves an HTTP **200** carrying an Incapsula challenge page to unproxied IPs — a block that does not announce itself as an error. This Actor sniffs for it, retries on a fresh proxy session, and never records it as "end of data". Apify's datacenter pool was measured clean; see the proxy FAQ. |

***

### 📈 Measured field fill

From a real run, counted against the **raw bytes** Copart returned, over **1,600 records** spanning
18 different scopes (whole index, single yard, single year, single state, by vehicle type, by title
group, buy-it-now only, free-text search, and a four-filter buyer query):

| Field | Fill | Field | Fill |
|---|---|---|---|
| `lotNumber`, `lotUrl`, `vin`, `year`, `make`, `model` | **100%** | `titleGroup`, `titleGroupCode`, `titleDescription`, `titleDocType` | 94.4% |
| `primaryDamage`, `damageTypeCode` | **100%** | `saleDate` *(default scope)* | 93.8% |
| `currentBid`, `hasBids`, `estimatedRetailValue` | **100%** | `fuel` | 89.9% |
| `saleType`, `saleTime`, `saleTimeZone`, `auctionName` | **100%** | `drive` | 86.6% |
| `yardName`, `yardNumber`, `city`, `state`, `zip`, `latitude`, `longitude` | **100%** | `engine`, `cylinders` | 86.1% |
| `keysStatus`, `color`, `thumbnailUrl` | 99.9% | `estimatedValue` | 83.2% |
| `titleState` | 99.8% | `odometer` | 81.3% |
| `modelGroup` | 99.4% | `trim` | 75.9% |
| `runAndDrive`, `engineStartProgram`, `lotFeatureCodes` | 98.3% | `repairCost` | 60.5% |
| `transmission` | 97.1% | `bidToRetailRatio` *(needs a bid)* | 46.3% |
| `hasKeys`, `odometerBrand`, `conditionCode` | 95%+ | `secondaryDamage` | 43.2% |

Every one of those gaps is **Copart's**, not the parser's: the offline suite asserts field-by-field
that our fill equals the fill in the raw bytes, and the build fails if it does not.

***

### 🧭 How the fields were named

Copart's search JSON names its columns with two- to four-letter keys — `hb`, `la`, `lotPlugAcv`,
`orr`, `dd`, `tgd`, `syn`. Guessing at those is how a scraper ships a confidently wrong column:
`la` and `lotPlugAcv` are both money in the same order of magnitude, and picking the wrong one puts
"estimated retail value" on a field that means something else, at 100% fill, with no error anywhere.

So none of it was guessed. The mapping was read out of **Copart's own front end** — the
`messages_CPRTUS_en` bundle it loads on every page, which carries the column dictionaries Copart
uses for its own search grid and CSV export:

| Copart's label id | Copart's own text | Our field |
|---|---|---|
| `app.label.csv.lotAcv` | Est. Retail value | `estimatedRetailValue` |
| `app.label.csv.lotPlugAcv` | Estimated value | `estimatedValue` |
| `lotsearch.header.label.highBid` | Current bid | `currentBid` |
| `lotsearch.header.label.repairCost` | Est. Cost of repair | `repairCost` |
| `lotsearch.header.label.buyItNowPrice` | Buy it now price | `buyItNowPrice` |
| `lotsearch.header.label.odometerReadingReceived` | Mileage | `odometer` |
| `lotsearch.header.label.saleTitleType` | Doc Type | `titleDocType` |

`currentBid` was then cross-checked a second way: it equals `dynamicLotDetails.currentBid` on
**1,600 of 1,600** rows, and the offline suite re-asserts that every build.

***

### ⚙️ Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `searchTerm` | string | — | Free text, exactly as Copart's own search box works. ANDs with every filter below. |
| `maxItems` | integer | `50` | Hard spend cap — rows are charged only as delivered. `0` = everything in scope. |
| `onlyUpcomingSales` | boolean | `true` | Restrict to lots with a future auction date. **Read the FAQ before turning this off.** |
| `states` | array | — | Two-letter yard states, e.g. `["TX","CA"]`. Filters Copart's `location_state`. |
| `yardNames` / `yardNumbers` | array | — | e.g. `"TX - DALLAS"` / `371`. The full list for your scope lands in `COPART_FACETS`. |
| `makes` / `models` / `years` | array | — | OR'd within each. |
| `yearFrom` / `yearTo` | integer | — | Model-year range. |
| `odometerMin` / `odometerMax` | integer | — | Odometer range. |
| `titleGroups` | array | — | `clean`, `salvage`, `non-repairable`. |
| `vehicleTypes` | array | — | 17 Copart categories from `automobile` to `agriculture-farm-equipment`. |
| `damageTypes` | array | — | All 25 Copart primary-damage codes. |
| `lotFeatures` | array | — | 16 Copart quick picks (`run-and-drive`, `buy-it-now`, `electric`, `classic`, `recovered-theft`…). **AND'd**, so two features narrow. |
| `saleDateFrom` / `saleDateTo` | string | — | `YYYY-MM-DD`. |
| `sortBy` | enum | `sale-date-soonest` | Also latest-first, Copart's own order, lot number, odometer. |
| `partitionBy` | enum | `auto` | `auto` / `yard` / `year` / `make` / `none`. |
| `rawFilters` | array | — | Raw clauses against Copart's index for anything the above misses. |
| `pageSize` | integer | `100` | Copart's own ceiling — it rejects 200 with an error. |
| `concurrency` | integer | `4` | |
| `proxyConfiguration` | object | Apify proxy | See the proxy FAQ. |

#### Example input

```json
{
  "titleGroups": ["salvage"],
  "lotFeatures": ["run-and-drive"],
  "makes": ["TOYOTA"],
  "yearFrom": 2018,
  "odometerMax": 80000,
  "states": ["TX", "CA"],
  "maxItems": 500
}
```

***

### 📤 Output

One row per lot. A real row from a real run, untouched:

```json
{
  "lotNumber": "41584556",
  "lotUrl": "https://www.copart.com/lot/41584556/salvage-2025-cadillac-escalade-esv-sport-vt-rutland",
  "vin": "1GYS9PRL5SR******",
  "vinMasked": true,
  "vinVisibleChars": 11,
  "year": 2025,
  "make": "CAD",
  "model": "ESCALADE E",
  "modelGroup": "ESCALADE",
  "trim": "SPORT",
  "title": "2025 CADILLAC ESCALADE ESV SPORT",
  "vehicleType": "SUV",
  "odometer": null,
  "odometerBrand": null,
  "primaryDamage": "FRONT END",
  "secondaryDamage": null,
  "damageTypeCode": "FR",
  "conditionCode": "CERT-E",
  "conditionDescription": "ENHANCED VEHICLES",
  "runAndDrive": false,
  "hasKeys": true,
  "keysStatus": "YES",
  "titleGroup": "SALVAGE TITLE",
  "titleState": "VT",
  "currentBid": 21800,
  "hasBids": true,
  "buyItNowPrice": null,
  "estimatedRetailValue": 60000,
  "estimatedValue": 96700,
  "repairCost": 41000,
  "bidToRetailRatio": 0.3633,
  "currency": "USD",
  "saleType": "Pure Sale",
  "saleDate": "2026-09-29T17:00:00.000Z",
  "auctionName": "VT - RUTLAND",
  "yardName": "VT - RUTLAND",
  "yardNumber": 88,
  "city": "RUTLAND",
  "state": "VT",
  "zip": "05701",
  "latitude": 43.5872,
  "longitude": -72.9784,
  "sellerDisclosed": false,
  "sourcePartition": "yard:Vt - Rutland",
  "sourceQuery": "UPCOMING=auction_date_utc:[NOW TO *] AND LOC=yard_name:\"VT - RUTLAND\"",
  "sourcePage": 0
}
```

Plus two key-value store records, neither of them billed:

- **`RUN_SUMMARY`** — the filters actually sent, how many lots Copart said matched, what each
  partition declared versus delivered, rows delivered (= rows charged), duplicates and empty records
  skipped, per-field fill measured on your own run, every failed page split into WAF blocks versus
  transport errors, and the honest-limits list.
- **`COPART_FACETS`** — Copart's entire filter vocabulary for your scope: every yard, make, model,
  year, body style, engine, damage type, title group and sale date in it, each with its live count.
  This is the input to your *next*, narrower run.

***

### 🏆 What this does that the alternatives do not

There are 26 other Copart Actors on the store (counted from the full store sitemap, all 12 shards).
This one is the cheapest of them, and it differs in five ways that are checkable rather than
claimed:

1. **`sellerName` — the consigning carrier**, on 18.2% of lots. Checked against all 26 other
   READMEs pulled from their own latest builds: **2 return it** (`scrapers_lat` $0.008/row, `bovi`
   $0.0233/row), one returns Copart's raw seller flags without resolving a name (`memo23`), and the
   other 23 do not return it at all. We return it at **$0.0006/row**.
2. **Coverage you can verify.** Every run reports Copart's own count for the scope against what was
   delivered, partition by partition, and **fails loudly** if a sweep comes back short. A short
   parse is otherwise indistinguishable from a small scope.
3. **Provenance on every row.** `sourcePartition`, `sourceQuery` and `sourcePage` mean a
   plausible-but-wrong result set is obvious at a glance instead of being taken on trust.
4. **Fields named from Copart's own dictionary**, not from inference — and re-asserted against the
   bytes on every build.
5. **No start fee**, against a lane where 22 of 26 rivals charge one.

***

### 💵 Pricing

| Event | Rate |
|---|---|
| Per lot returned (`lot-scraped`) | **$0.0006** per row — **$0.60 per 1,000** |
| Actor start | **none — there is no start fee** |
| Everything else (`RUN_SUMMARY`, `COPART_FACETS`, retries, skipped duplicates, unreadable records) | **free** |

Rows are charged **as they are delivered**, per item. If you set a budget cap, you keep exactly the
rows you paid for — you will never be handed rows you were not billed for, or billed for rows you
were not handed. A duplicate skipped across partitions, a record with no lot number, and a retried
request all cost you nothing.

The default `maxItems` of 50 means an empty input costs **$0.03**.

***

### ❓ FAQ

#### Why does `onlyUpcomingSales` default to true, and what happens if I turn it off?

Because of a sharp edge worth knowing about. Copart's public index holds **417,320** lots, but only
**143,154 (34.3%)** have an auction date in the future — the rest are listed but not yet scheduled.
Copart's index sorts the *undated* lots **first** on any ascending date sort, so a "soonest first"
run over the whole index returns 100 lots nobody can bid on and looks completely healthy doing it.
Leaving this on gives you the lots that are actually for sale. Turn it off when you want total
inventory, including cars that have arrived at a yard but have no sale scheduled yet.

#### Is the VIN complete?

**No.** Copart returns 11 of the 17 characters to anonymous callers: `KNDJT2A64C7******`. Every row
says so explicitly via `vinMasked` and `vinVisibleChars`. The visible 11 still carry the WMI,
vehicle descriptor and check digit — enough to decode make, model, body, engine, restraint system
and model year, and enough to match against a VIN you already hold — but they are **not** enough to
uniquely identify the vehicle on their own. Any Copart Actor showing you a full 17-character VIN in
its sample output is not showing you what the anonymous API returns.

#### Can I get sold prices or auction results?

Not from this Actor, and not from Copart's public surface. Copart does not publish realized prices
to anonymous callers — every lot in the public index is an open lot. `currentBid` is the live high
bid when the request was made. For pre-sale price context the row carries Copart's own
`estimatedRetailValue`, `estimatedValue` and `repairCost`, plus a derived `bidToRetailRatio`.

#### Who is the seller?

On **18.2%** of lots Copart names the consigning company in `sellerName` — the insurers writing
these vehicles off. Measured over 1,600 rows: GEICO (152), USAA (68), Bristol West (18), CSAA (15),
Farmers (14), AIG, National Powersport Auctions. On the other 81.8% Copart consigns anonymously and
`sellerDisclosed` is `false`. Two of the 26 other Copart Actors return this field too, at $0.008
and $0.0233 per row. It is a **company name on a vehicle record** — there is no person, email,
phone or address behind it, on this surface or any other public one. This is vehicle data, not
contact data.

#### Why is `estimatedRetailValue` sometimes null when Copart shows a number?

It is null when Copart has no value for it. Watch for this if you are parsing Copart yourself:
Copart writes **`-1`**, not `0` and not null, as its not-published sentinel on that field — on 269
of 1,600 measured rows. A zero check does not catch it, so it sails through as a retail value of
minus one dollar and poisons anything derived from it. Here it becomes `null`, and the offline
suite fails the build if a negative ever reaches a money column.

#### `yardNumber` and the auction don't always match — why?

Because they are different things, and this trips up naive Copart parsers. `yardNumber` is the
**physical** yard the car sits in; `auctionHostNumber` is the **sale** it has been assigned to, and
Copart runs virtual sales (`MEDIUM DUTY CLEAN TITLE SALE`, `RENTAL VEHICLE SALE`) out of physical
yards. Filtering yard 371 returns 200 cars all physically at 371, 8 of which belong to sale 832 or
838\. `yardNumber` here always agrees with `yardName`; the sale keeps its own field.

#### Which proxy should I use?

The default (Apify's automatic/datacenter pool) was measured clean across several hundred requests
on 2026-09-18 and is the fastest (~1.7 s/request). If you see blocks, switch to residential:
`{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US"}` —
measured working at ~4.0 s/request. Your own proxies work too. Running **unproxied** is not
recommended: copart.com sits behind Imperva and intermittently serves an HTTP 200 carrying a
challenge page to bare IPs (it did exactly that to the machine this was built on, then stopped
about ninety minutes later).

#### What happens when Copart blocks a request?

It is retried on a **fresh proxy session** — retrying a flagged IP through the same IP is a slower
way to get the same answer. A block is never recorded as "no more results": the Actor tells apart a
transport failure, a WAF challenge served under HTTP 200, a malformed query and a genuinely empty
result set, because those need opposite fixes. Failed pages are itemised in `RUN_SUMMARY`.

#### How do you handle Copart's pagination limits?

Copart caps page size at 100 (it rejects 200 outright) but deep paging works: pages at
`start=120,000` and beyond were measured returning real, distinct rows with zero overlap against
page 0. The catch is that the index is **live** — rows shift between requests, so a single long
walk silently loses whatever slides backwards past the cursor. Large scopes are therefore split
along a facet Copart itself publishes (yard, then year, then make), each partition small enough to
close in seconds and each checked against Copart's own count for it. It is also cheaper: Copart
repeats a ~425 KB facet dictionary on every page, and a narrower filter shrinks it by 36–56%.

#### Does partitioning miss anything?

It would, if it were done naively — Copart's yard facet panel caps at 400 values and names fewer
lots than the scope contains. So every partitioned run computes the shortfall and sweeps it with an
explicit **remainder query** that excludes the named values, and reports the arithmetic in
`RUN_SUMMARY`.

#### Can I filter on something that isn't in the input list?

Yes — `rawFilters` takes raw clauses against Copart's own index, AND'd with everything else, e.g.
`body_style:"4DR SEDAN"` or `lot_features_code:LOTFEATURE_X`. The complete vocabulary for your
scope, with live counts, is written to `COPART_FACETS` on every run.

#### How many requests does a row cost?

One request per 100 rows, and **no per-lot request at all** — all the fields arrive in the search
response. That is why this runs at 512 MB with no browser.

#### Will this get me Copart bidding access?

No. This reads public listing data. Buying at Copart requires a Copart membership and, for many
lots, a dealer or dismantler licence in the relevant state. `lotFeatures: ["no-license-required"]`
filters to lots Copart marks as open to the public.

#### What does `robots.txt` say?

`www.copart.com/robots.txt` disallows a set of member-account paths. The two lines that bear on this
Actor, verbatim:

```
Disallow: /public/data/
Allow: /lotSearchResults$
```

The endpoint this Actor uses, **`/public/lots/search-results`**, matches no `Disallow` line. The
`/public/data/` lot-detail endpoint does match one — so it is **not used**, which also costs nothing,
because it adds only 12 low-value flags over the search record. The gated member areas (`/myBids/`,
`/dashboard/`, `/downloadSalesData`) are never touched, and the offline suite fails the build if any
source file acquires a request to a disallowed path.

#### Is this legal to use?

This Actor reads publicly published listing data — no login, no account, no circumvention of an
access control, and no personal data. What you do with it is your call: check your own jurisdiction
and Copart's Terms of Use before commercial redistribution. Salvage-title disclosure is a federal
and state requirement (NMVTIS, 49 U.S.C. §30502 / 28 CFR Part 25), which is why these vehicle facts
are published in the first place.

***

### 🔧 How it works

```
POST /public/lots/search-results   page 1 of the scope  ->  totalElements + the facet index
  └─ plan the walk                 split large scopes along a facet Copart publishes
     └─ POST .../search-results    pages 2..N, <= 100 rows each, 4 in flight, deduped by lot number
        └─ push + charge           atomically, per item, only for rows actually delivered
           └─ floor-assert         delivered vs Copart's own count; a short sweep FAILS the run
```

No browser, no per-lot hop, no headless Chrome — one JSON endpoint, 512 MB, and the discipline to
tell a block from an empty page.

# Actor input Schema

## `searchTerm` (type: `string`):

Free-text search across Copart's index, exactly as the site's own search box works (e.g. 'camry', 'tesla model 3', 'freightliner'). Combines with every filter below as an AND. Leave empty to scope by filters alone.

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

Maximum lots to return and charge for. Rows are charged only as they are delivered, so this is a hard spend cap. Set 0 for every lot in scope (417,000+ with no filters — expect a long run).

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

Two-letter state/province codes of the Copart yard holding the lot (e.g. TX, CA, FL, ON). Filters on Copart's own location\_state index field, not on the printed yard name.

## `yardNames` (type: `array`):

Exact Copart yard names as printed on the site, e.g. 'TX - DALLAS' or 'WI - MILWAUKEE SOUTH'. The full list for your scope is written to the COPART\_FACETS key-value store record on every run.

## `yardNumbers` (type: `array`):

Copart numeric yard identifiers, e.g. 371. Equivalent to Yard names but immune to yard renames.

## `makes` (type: `array`):

Vehicle makes as Copart spells them, e.g. TOYOTA, FORD, BMW, KIA. Multiple makes are OR'd together.

## `models` (type: `array`):

Vehicle models as Copart spells them, e.g. CAMRY, F150, SILVERADO. Multiple models are OR'd together.

## `years` (type: `array`):

Exact model years to include, e.g. 2020, 2021. Use Year from / Year to for a range instead.

## `yearFrom` (type: `integer`):

Earliest model year to include (inclusive). Combine with Year to for a range.

## `yearTo` (type: `integer`):

Latest model year to include (inclusive).

## `odometerMin` (type: `integer`):

Lowest odometer reading to include, in the unit Copart recorded. Note Copart stores 0 for many lots where no reading was received.

## `odometerMax` (type: `integer`):

Highest odometer reading to include.

## `titleGroups` (type: `array`):

Copart title groups: clean, salvage, non-repairable. Multiple values are OR'd.

## `vehicleTypes` (type: `array`):

Copart vehicle categories. Multiple values are OR'd together.

## `damageTypes` (type: `array`):

Copart primary damage codes. Multiple values are OR'd together.

## `lotFeatures` (type: `array`):

Copart's own quick-pick conditions. Multiple values are AND'd, so selecting Run and drive plus Buy it now narrows to lots that are both.

## `saleDateFrom` (type: `string`):

Earliest auction date to include, as YYYY-MM-DD. Copart's public index holds upcoming sales only.

## `saleDateTo` (type: `string`):

Latest auction date to include, as YYYY-MM-DD.

## `onlyUpcomingSales` (type: `boolean`):

Measured 2026-09-18: only 143,154 of the 417,320 lots in Copart's public index (34.3%) have an auction date in the future. The rest are listed but not yet scheduled, and Copart sorts those undated lots FIRST on any ascending date sort - so leaving this off makes a capped soonest-first run return lots nobody can bid on. Turn it off to pull the whole index including unscheduled lots.

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

Order Copart returns lots in. Sale date soonest is the most useful for a capped run, and only means what it says while Only lots with an upcoming sale date is on - Copart sorts undated lots to the front otherwise. Copart's own default puts dated lots first but does not order them by date.

## `partitionBy` (type: `string`):

How to split a scope too large for one paged walk. Copart's index is live and shifts rows between requests, so partitioning into small sub-queries makes coverage verifiable (each partition's row count is checked against Copart's own count for it) and cuts transport cost, because the repeated facet block shrinks with a narrower filter. Auto picks yard, then year, then make.

## `rawFilters` (type: `array`):

Escape hatch for anything the inputs above do not cover: raw clauses against Copart's own search index, AND'd with everything else. Example: body\_style:"4DR SEDAN" or lot\_features\_code:LOTFEATURE\_X. The vocabulary for your scope is in the COPART\_FACETS key-value store record.

## `pageSize` (type: `integer`):

Lots fetched per request. Copart rejects anything above 100 with an error, so 100 is both the default and the ceiling. Lower it only to debug.

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

Parallel requests against Copart. Four is a good balance; raising it risks tripping Copart's Imperva rate limits, which cost more in retries than the parallelism saves.

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

copart.com sits behind Imperva and intermittently serves an HTTP 200 challenge page to unproxied IPs. Apify's datacenter pool was measured clean on 2026-09-18 and is the default; switch to RESIDENTIAL with country US if you see blocks.

## Actor input object example

```json
{
  "searchTerm": "tesla model 3",
  "maxItems": 50,
  "states": [
    "TX",
    "CA"
  ],
  "yardNames": [
    "WI - MILWAUKEE SOUTH"
  ],
  "yardNumbers": [
    "371"
  ],
  "makes": [
    "TOYOTA",
    "HONDA"
  ],
  "models": [
    "CAMRY"
  ],
  "years": [
    "2022",
    "2023"
  ],
  "yearFrom": 2018,
  "yearTo": 2026,
  "odometerMax": 80000,
  "titleGroups": [
    "salvage"
  ],
  "vehicleTypes": [
    "automobile"
  ],
  "damageTypes": [
    "front-end"
  ],
  "lotFeatures": [
    "run-and-drive"
  ],
  "saleDateFrom": "2026-09-21",
  "saleDateTo": "2026-09-30",
  "onlyUpcomingSales": true,
  "sortBy": "sale-date-soonest",
  "partitionBy": "auto",
  "rawFilters": [
    "body_style:\"4DR SEDAN\""
  ],
  "pageSize": 100,
  "concurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per Copart lot. Vehicle identity (year, make, model, model group, trim, the masked VIN and how many of its 17 characters are visible, engine, cylinders, fuel, transmission, drive, colour). The salvage disclosure (primary and secondary damage plus Copart's damage code, title group, title description, title state, title doc type, odometer with its brand and unit, run-and-drive and engine-start certification, keys). The money (current bid, whether any bid exists, buy-it-now price, Copart's own estimated retail value, estimated value and estimated cost of repair, plus a derived bid-to-retail ratio). The sale (date, time, time zone, auction name, sale type, lot status). The yard (name, number, city, state, country, ZIP, latitude, longitude). And the provenance of the row itself: which partition and which query returned it, and on which page.

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

RUN\_SUMMARY: the filters actually sent to Copart, how many lots Copart's own index said match, how the scope was partitioned and what each partition declared versus delivered, rows delivered (which equals rows charged), per-field fill measured on this run's own rows, every page that failed and whether it failed as a WAF block or a transport error, and an honest-limits list covering the VIN masking, the absence of sold prices and the anonymity of Copart's consignors.

## `facets` (type: `string`):

COPART\_FACETS: Copart's own facet index for the scope you ran, written free of charge and never billed. It is the complete list of yards, makes, models, model groups, years, body styles, engines, damage types, title groups, vehicle types and sale dates present in that scope, each with its live lot count - i.e. the exact vocabulary for building your next, narrower 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 = {
    "searchTerm": "",
    "maxItems": 50,
    "states": [],
    "yardNames": [],
    "yardNumbers": [],
    "makes": [],
    "models": [],
    "years": [],
    "titleGroups": [],
    "vehicleTypes": [],
    "damageTypes": [],
    "lotFeatures": [],
    "onlyUpcomingSales": true,
    "sortBy": "sale-date-soonest",
    "partitionBy": "auto",
    "rawFilters": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/copart-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 = {
    "searchTerm": "",
    "maxItems": 50,
    "states": [],
    "yardNames": [],
    "yardNumbers": [],
    "makes": [],
    "models": [],
    "years": [],
    "titleGroups": [],
    "vehicleTypes": [],
    "damageTypes": [],
    "lotFeatures": [],
    "onlyUpcomingSales": True,
    "sortBy": "sale-date-soonest",
    "partitionBy": "auto",
    "rawFilters": [],
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/copart-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 '{
  "searchTerm": "",
  "maxItems": 50,
  "states": [],
  "yardNames": [],
  "yardNumbers": [],
  "makes": [],
  "models": [],
  "years": [],
  "titleGroups": [],
  "vehicleTypes": [],
  "damageTypes": [],
  "lotFeatures": [],
  "onlyUpcomingSales": true,
  "sortBy": "sale-date-soonest",
  "partitionBy": "auto",
  "rawFilters": []
}' |
apify call scrapersdelight/copart-lot-scraper --silent --output-dataset

```

## MCP server setup

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