# Bring a Trailer Scraper - Sold Prices, VIN & Results (`scrapersdelight/bringatrailer-results-scraper`) Actor

From $1.30 per 1,000 results, no start fee. Every completed Bring a Trailer auction as one row: chassis/VIN, realized sale price, sale date, lot number, city and postal code, bid count, odometer, make and model. 209,076 sold results, and it pages past BaT's 10,000-row search limit.

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

## Pricing

$1.30 / 1,000 per auction result delivereds

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

## 🚗 Bring a Trailer Scraper — sold prices, chassis numbers and auction results

**A VIN-keyed price database for collector cars.** Every completed Bring a Trailer auction comes
back as one row: the **chassis / VIN number**, what it **actually sold for**, the date, the lot
number, the odometer, where the car was, who sold it and how many bids it took.

> **Read this first: this is a price database, not a lead list.** BaT sellers are pseudonymous
> handles — `CobraKid`, `stim4u` — and Bring a Trailer publishes **no seller phone number, no email
> and no company name**, anywhere, for anyone. This Actor collects what BaT shows publicly on a
> completed listing. If you came here for contact details for the people selling cars, this Actor
> cannot give you them and no BaT scraper can. What it *can* give you is the comps table behind a
> valuation.

Scope is deliberately narrow: **completed auctions only.** Live auctions in progress are not
polled and current bids are not tracked.

***

### 📊 What one row contains

| | Field |
|---|---|
| **Identity** | `listingId` · `url` · `lotNumber` · **`chassis`** (VIN / chassis number) · `title` |
| **Vehicle** | `year` · `make` · `model` · `modelUrl` · `era` · `origin` · `category` |
| **The result** | `status` · **`soldPrice`** · `highBid` · `currency` · `priceText` · `resultDate` · `resultAt` · `endedAt` · `bidCount` · `commentCount` |
| **Flags** | `noReserve` · `premium` · `batAlumni` · `verifiedCheckout` · `priceSourceDisagreement` |
| **Condition** | `mileage` · `mileageUnit` · `mileageText` · `engine` · `transmission` · `exteriorColor` · `interiorColor` · `essentials` |
| **Parties** | `sellerUsername` · `sellerProfileUrl` · `sellerType` (private / dealer) · `buyerUsername` |
| **Location** | `locationText` · `city` · `region` · `postalCode` · `countryCode` · `countryCodeAlpha3` |
| **Interest** | `views` · `watchers` |
| **Media & copy** | `thumbnailUrl` · `imageCount` · `imageUrls` (opt-in) · `excerpt` · `description` (opt-in) |
| **Provenance** | `detailFetched` · `sourceQuery` · `sourceUrl` · `scrapedAt` |

#### A real row, from a real run

```json
{
  "listingId": 121100523,
  "url": "https://bringatrailer.com/listing/1997-porsche-911-turbo-s-24/",
  "lotNumber": "263485",
  "chassis": "WP0AC2996VS375797",
  "title": "1997 Porsche 911 Turbo S",
  "year": 1997,
  "make": "Porsche",
  "model": "Porsche 993 911 Turbo S",
  "modelUrl": "https://bringatrailer.com/porsche/993-911-turbo-s/",
  "era": "1990s",
  "origin": "German",
  "status": "sold",
  "soldPrice": 852000,
  "highBid": 852000,
  "currency": "USD",
  "priceText": "Sold for USD $852,000 on 9/18/2026",
  "resultDate": "2026-09-18",
  "endedAt": "2026-09-18T17:24:34.000Z",
  "bidCount": 33,
  "commentCount": 212,
  "noReserve": false,
  "premium": true,
  "batAlumni": false,
  "mileage": 24000,
  "mileageUnit": "miles",
  "mileageText": "24k Miles Shown",
  "engine": "Twin-Turbocharged 3.6L Flat-Six",
  "transmission": "Six-Speed Manual Transaxle",
  "exteriorColor": "Guards Red Paint",
  "interiorColor": "Supple Black Leather Upholstery",
  "sellerUsername": "stim4u",
  "sellerType": "private",
  "buyerUsername": "gosmarte",
  "city": "Fairfield",
  "region": "Connecticut",
  "postalCode": "06824",
  "countryCode": "US",
  "views": 28033,
  "watchers": 2542,
  "imageCount": 222
}
```

***

### 🎯 Who this is for

| Buyer | What they do with it |
|---|---|
| **Valuation and insurance data vendors** | Join `chassis` to their own VIN records and build a realized-price series per model. `modelUrl` is BaT's own normalised model key, so "993 911 Turbo S" groups correctly without string matching. |
| **Classic-car dealers and consignment shops** | Price inventory against what comparable cars actually made, filtered by year, mileage and colour. |
| **Collector-car lenders** | Support a loan-to-value against public transaction evidence rather than an asking price. |
| **Auction houses** | Benchmark their own estimates against BaT results for the same model and year. |
| **Market analysts and journalists** | Track a model's price curve, no-reserve premium, or bid counts over a decade of results. |

***

### 🧭 What makes this different from the other BaT scrapers

There are **21 Bring a Trailer Actors on the Apify Store** (counted from the full store census —
`apify.com/sitemap/actors1..12.xml`, 599,202 URLs, 70,945 of them Actor records). Three of them have
real monthly usage. Here is what this one does that they do not, each item measured rather than
claimed.

#### 1. It reports the price the car actually sold for

BaT ends some auctions with a **post-auction sale**: the reserve was not met during bidding, and the
car sold afterwards at a different price. On those listings BaT's own list feed still reports the
final **bid** in `current_bid`, while the **sale** price is in the result line.

Measured on a 400-row live run: **12 rows (3.0%) were post-auction sales**, and **11 of them had a
sale price different from the final bid** — `$8,500` bid against `$13,000` sold, `$13,500` against
`$16,750`, `$111,000` against `$115,000`, `$23,250` against `$23,750`. A comps table built on
`current_bid` is understated on those rows by up to 53%, always in the same direction.

This Actor carries both: `soldPrice` (what it sold for), `highBid` (the final bid), a `status` of
`sold` / `sold-after-auction` / `reserve-not-met`, and a `priceSourceDisagreement` object on the
rows where they differ, so you can see them rather than average them in.

#### 2. It gets past BaT's 10,000-row search limit

BaT's results endpoint is ElasticSearch-backed and inherits the default 10,000-document window.
Measured by binary search on 2026-09-18 against `state=sold`:

| Rows per page | Last page that returns anything | Rows reachable |
|---|---|---|
| 36 | 277 | 9,972 |
| 48 | 208 | 9,984 |
| **60** | **166** | **9,960** |

Past that, BaT answers **HTTP 200 with `items_total: 0` and an empty array** — while `pages_total`
goes on claiming 5,808 pages. There is no error to catch. A scraper that pages until it sees no
rows reports a clean, successful, complete sweep of the archive after reading **3.8%** of it. One
rival's README advertises "up to a million per run" from this endpoint; the endpoint will not give
anyone more than 9,984 rows for one query.

This Actor handles it two ways:

- **Two-ended reading.** `sort=td` and `sort=ta` walk the same result set from opposite ends
  (verified: zero id overlap between the first 60 of each on a 10,421-row set), so any query with up
  to **19,920** hits is read completely.
- **Model-year partitioning.** Anything bigger is split one model year at a time, then by category,
  then by origin, deduplicating on listing id. Measured across all 127 model years from 1900 to
  2026: **200,070 of 209,076** sold listings carry an indexed model year (95.7%), and the largest
  single year is 2006 at 5,437 — comfortably inside one window.

Either way, `RUN_SUMMARY` records what BaT said was there against what was actually read, per
partition. **A partial sweep is never reported as a complete one.**

#### 3. Fields the others advertise but cannot fill

Two competing Actors list `latitude` and `longitude` among their output fields, and one's README
shows a worked example with coordinates in it. **BaT's feed does not publish them.** Measured across
155 feed rows on 2026-09-18: `lat` and `lon` were `null` on **155 of 155**, along with `country`,
`era`, `categories`, `year`, `in_subscription`, `keyword_pages`, `processing_type` and `searchable`.
This Actor does not ship a latitude column; it ships `city`, `region`, `postalCode` and
`countryCode`, read off the listing page, filled on 100% of sampled listings.

The same applies to engagement. The feed's `comments` is the literal string `"0"` and `views` is
`"0 Views"` on every row — placeholders the browser fills in over a websocket. Read naively they
make every car look like it drew no interest. This Actor takes `views`, `watchers` and
`commentCount` from the listing page, or leaves them **null** if you turn listing pages off. Never a
fabricated zero.

#### 4. No run-start fee

All 21 rival Actors charge one, from $0.00005 to $0.10 per run — one of them charges more to start
a run than this Actor charges for 76 rows. This one charges **nothing** to start and **nothing**
for a run that matches nothing.

***

### 📈 Measured coverage and field fill

Counted on **2026-09-18** against real bytes fetched through the Apify proxy — not estimates, and
not copied from a competitor.

**The corpus.** BaT publishes **263,924** completed listings: **209,076 sold** and **54,848** ended
below reserve. (209,076 + 54,848 = 263,924 exactly — the sold/unsold facet is a real server-side
split, not a filter applied after download.)

**Field fill on a real 400-row run** — the 400 most recently closed sold listings, every category
included, nothing filtered out to flatter the numbers. These are the figures `RUN_SUMMARY` printed
for that run:

| Field | Fill | Note |
|---|---|---|
| `listingId`, `url`, `lotNumber`, `title`, `status`, `highBid`, `currency`, `priceText`, `resultDate`, `resultAt`, `endedAt`, `bidCount`, `commentCount`, `essentials`, `sellerUsername`, `sellerType`, `buyerUsername`, `locationText`, `city`, `region`, `countryCode`, `views`, `watchers`, `thumbnailUrl`, `imageCount`, `excerpt` | **100%** | 400/400 |
| `soldPrice` | 99.5% | |
| `make` | 99.5% | |
| `origin` | 99.3% | |
| `model`, `modelUrl` | 97.5% | |
| `postalCode` | 97.3% | |
| `era` | 92.0% | |
| **`chassis` (VIN)** | **91.3%** | 365/400 across *all* categories. See below. |
| `transmission` | 90.8% | |
| `year` | 89.8% | Parsed from the title and cross-checked against BaT's own era tag — **151 of 151** listings that carried both agreed. |
| `mileage` | 88.3% | |
| `interiorColor` | 85.3% | Derived from BaT's own "Listing Details" bullets. |
| `engine` | 82.3% | Same. |
| `exteriorColor` | 75.8% | Same. |
| `category` | **67.8%** | BaT only tags a listing with a category when it fits one of its 25. A 1995 Ferrari with no tag will not match a `Convertibles` filter even if it is one. |
| `priceSourceDisagreement` | 2.8% | Fires only on post-auction sales — see below. |

**The chassis caveat that matters.** The 91.3% above is across everything BaT sells, including lots
that have no VIN because they are not vehicles. Of the 35 rows with no chassis number, **32 were
BaT's own Parts or Wheels categories** (gas pumps, wall art, a set of Fuchs wheels, a racing
simulator) and the other three were an engine project, a bicycle project and one truck. **On
vehicles the fill is 365 of 368 — 99.2%**, and on a separate 154-listing sample weighted towards
cars and motorcycles it was **153 of 154, 99.4%**. Filter out `Parts` and `Wheels` and expect a VIN
on essentially every row; leave them in and expect correct nulls.

**What the same run showed about the rest of the corpus:** 388 of 400 sold during the auction and
**12 (3.0%) were post-auction sales**, of which **11** had a sale price different from the final
bid. 398 rows were priced in USD and 2 in EUR. 389 cars were in the US, 7 in Canada and one each in
Japan, Italy, Switzerland and Belgium. The median gallery held **151 photos**.

**Speed.** 400 rows with full listing-page enrichment: **141 seconds** at 1 GB, 400 × HTTP 200, 0
unreachable listings, 0 truncated bodies, 7 API requests. An earlier 155-page burst at 10 in
parallel ran in 31 seconds. The list feed costs ~300 bytes of wire per row; a listing page is ~74 KB
on the wire (about 500 KB uncompressed).

***

### ⚙️ How to use it

#### The quickest useful run

```json
{
  "searchTerms": ["porsche 911"],
  "minYear": 1995,
  "maxYear": 1999,
  "maxResults": 100
}
```

#### Everything a model sold for, ever

Paste the BaT model page and let the Actor read BaT's own curated listing set off it:

```json
{
  "startUrls": [{ "url": "https://bringatrailer.com/porsche/911/" }],
  "maxResults": 0
}
```

#### A results page you already filtered by hand

Set the filters on `bringatrailer.com/auctions/results/`, copy the URL, paste it. Every parameter —
`search`, `result`, `yearFrom`, `yearTo`, `category`, `era`, `origin`, `location`, `type`,
`timeFrame`, `bidTo`, `exclude`, `sortby` — is translated:

```json
{
  "startUrls": [{
    "url": "https://bringatrailer.com/auctions/results/?search=e30+m3&result=sold&yearFrom=1988&yearTo=1991"
  }],
  "maxResults": 500
}
```

#### A comps table, one row per car

```json
{
  "categories": ["Motorcycles"],
  "origins": ["Japanese"],
  "minYear": 1970,
  "maxYear": 1985,
  "dedupeByChassis": true,
  "maxResults": 0
}
```

#### Prices only, eight times faster

```json
{
  "soldWithin": "30D",
  "fetchDetails": false,
  "maxResults": 0
}
```

Turning listing pages off drops `chassis`, `lotNumber`, location, seller, buyer, `bidCount`,
odometer, engine, transmission, colours, `essentials`, `views`, `watchers`, `commentCount` and the
photo count. Price, date, title, flags, country and the listing URL still come through. **The
per-row price is the same either way** — turn it off for speed, not for cost.

#### Every input

| Input | What it does |
|---|---|
| `result` | `sold` (default, 209,076 rows), `reserve-not-met` (54,848), or `all`. BaT's own server-side facet. |
| `searchTerms` | Words that must appear, e.g. `["porsche 911"]`. |
| `excludeTerms` | A word that must not appear, e.g. `project`. |
| `startUrls` | A model/make page, a results page with filters, or a single listing. |
| `minYear` / `maxYear` | Model-year range. |
| `eras` | BaT's 14 decade tags. |
| `categories` | BaT's 25 category tags. |
| `origins` | BaT's 14 origin tags. |
| `locations` | 38 countries plus BaT's three region groups. |
| `listingTypes` | `No Reserve`, `Premium`, `BaT Alumni`. |
| `maxPriceUsd` | Keep only results at or below this. |
| `soldWithin` | `7D`, `30D`, `1Y`, `2Y`, `5Y`, or all time. |
| `sort` | Recently closed / oldest / most viewed / highest bid. |
| `maxResults` | Hard cap on rows delivered **and charged**. `0` = no cap. |
| `fetchDetails` | Open each listing page. Default **on**. |
| `includePhotos` | Add every gallery URL (median 151 per listing, measured). |
| `includeDescription` | Add the full write-up (~2,000–4,400 characters; the always-included `excerpt` is BaT's ~350-character teaser). |
| `dedupeByChassis` | One row per VIN — collapses repeat sales of the same car. |
| `autoPartition` | Work around the 10,000-row search limit. Default **on**. |
| `detailConcurrency` | Listing pages in parallel. Default 8. |
| `proxyConfiguration` | Apify Proxy, datacenter by default. |

Every filter except `dedupeByChassis` is applied by **Bring a Trailer**, before a row is sent, so
filtered-out listings are never downloaded and never charged.

***

### 💵 Pricing

**$0.0013 per result delivered — $1.30 per 1,000. No run-start fee.**

| Results | Cost |
|---|---|
| 50 (the default run) | $0.07 |
| 100 | $0.13 |
| 1,000 | $1.30 |
| 10,000 | $13.00 |
| 0 rows matched | **$0.00** |

You are charged once per row **delivered to your dataset**, and the `maxResults` cap is counted
against rows produced rather than batches flushed — a cap of 60 charges exactly 60. Opening each
listing page for the chassis number and the rest is included in the rate; so is the partitioning
that gets past BaT's search limit.

How that compares over the same 200-row job. Rates were read from each Actor's live `pricingInfos`
on 2026-09-18, taking the FREE-tier rate where the Actor uses tiered pricing, and the run-success
figures are each Actor's own public 30-day stats:

| Actor | Per result | Start fee | 200 results | Publishes VIN? |
|---|---|---|---|---|
| **This Actor** | **$0.0013** | **none** | **$0.26** | **Yes — 99.2% fill on vehicles** |
| `jongoose/bring-a-trailer-scraper` | $0.001 | $0.00005 | $0.20 | **No** — no VIN or chassis field |
| `mrdoe/bringatrailer-car-data-scraper-us` | $0.0015 | $0.00005 | $0.30 | Yes |
| `devilscrapes/bring-a-trailer-auctions-scraper` | $0.0015 | $0.005 | $0.30 | Yes |
| `tortuga/bringatrailer-scraper` | $0.003 + $0.004 detail | none | $1.40 | Yes |
| `lulzasaur/bringatrailer-scraper` | $0.005 | $0.00005 | $1.00 | No |
| `silentflow/bringatrailer-scraper` | $0.006 | $0.00005 | $1.20 | Yes |
| `parseforge/bringatrailer-auctions-scraper` | $0.015 | $0.05 | $3.05 | No |

One Actor on the Store is cheaper per row, and it is listed above rather than left out: `jongoose`
at $0.001 will save you six cents on a 200-row job. It publishes no VIN or chassis number — the
field this product is built around — so if you only need sold prices and dates it is the cheaper
choice, and you should take it. **Of the Actors that do publish a VIN, this is the cheapest**, and
it is the only one of any of them with no run-start fee at all.

***

### ❓ FAQ

**Does this give me the seller's phone number or email?**
No, and nothing can. Bring a Trailer identifies sellers by a pseudonymous handle and publishes no
phone, email or company name. You get `sellerUsername`, a link to their public BaT profile, and
whether BaT lists them as a private party or a dealer. If you need contact data, this is the wrong
Actor — it is a price database.

**Is the chassis number really a VIN?**
It is whatever the seller entered in BaT's "Chassis" field, which for a modern car is the 17-digit
VIN and for a pre-1981 car is the manufacturer's chassis number. It is delivered verbatim, not
validated or checksummed. Fill measured at 99.4% on vehicles and 66.7% on a sample weighted towards
parts and wheels lots, which have no chassis at all.

**Can I get all 209,076 sold results in one run?**
Set `maxResults: 0` and leave `autoPartition` on. The Actor will walk it one model year at a time.
Two things to know first: at $0.0013 a row that is $272, and the model-year axis reaches 95.7% of
the corpus — the remaining ~9,000 listings (parts, wheels, and a few lots BaT indexes with no year)
are not addressable through it. `RUN_SUMMARY` tells you exactly what was read against what BaT said
was there.

**Why is `category` empty on so many rows?**
Because BaT leaves it empty. A category tag is applied only when a listing fits one of BaT's 25
categories, and about a third of listings carry none. Filtering by category is therefore narrower
than it looks.

**What is `sold-after-auction`?**
The auction ended below reserve and the car sold afterwards, usually at a higher number than the
final bid. BaT labels the result row "Sold" instead of "Winning Bid". Those rows carry both numbers
and a `priceSourceDisagreement` object.

**Does it track live auctions or current bids?**
No. Completed results only, by design.

**How current is the data?**
As current as the site. A listing appears in the feed the moment its auction ends; results captured
during testing included auctions that had closed within the hour.

**What currency are the prices in?**
BaT quotes in USD. `currency` is read from the listing rather than assumed, and it was `USD` on
155 of 155 sampled rows, including a Canadian listing.

**Does it need a login?**
No. Everything it reads is public and no account is used. BaT's own results page demands a login
nonce only for the logged-in-user filters (watched / following / admin), which this Actor does not
touch.

**Can I run it on a schedule to catch new results?**
Yes. `soldWithin: "7D"` with `fetchDetails: true` gives you the week's results — measured at 1,005
sold in the last 7 days. Deduplicate on `listingId` in your own store.

**What happens if a listing page can't be fetched?**
It is retried on a fresh proxy session. If it still fails, the row is delivered from the feed alone
with `detailFetched: false`, and the URL is listed in `RUN_SUMMARY.listingsUnreachableAfterRetries`.
A row that could not be identified at all is neither delivered nor charged.

**Why does a run sometimes make more requests than rows?**
Each partition costs one probe request to learn how many rows BaT says it holds. On a big
partitioned sweep that is roughly 130 extra requests for the whole model-year axis. They are not
charged.

***

### 🔍 Honest limits

- **No seller contact data exists on BaT.** Stated three times in this README because it is the
  single most common wrong expectation about this site.
- **The model-year sweep reaches 95.7% of the sold archive**, measured. Listings BaT indexes with no
  model year — parts, wheels, some memorabilia — cannot be reached through that axis.
- **`engine`, `transmission`, `exteriorColor` and `interiorColor` are inferred** from BaT's free-text
  "Listing Details" bullets, not from structured fields. The full bullet list is always delivered in
  `essentials` so you can re-derive them yourself. Measured fill 79–99%.
- **`year` is parsed from the listing title**, because BaT does not publish it in the feed even
  though it indexes it. It is cross-checked against BaT's era tag: 151 of 151 agreed.
- **`mileage` is what the seller claimed**, expanded from BaT's own wording (`24k Miles Shown` →
  24,000). `mileageText` keeps the original, including qualifiers like TMU.
- **Bidder and seller handles are pseudonyms**, published by BaT on the public listing page. No
  real names, no contact details, no attempt to resolve identities.
- **BaT occasionally answers 403 to an Apify datacenter IP** — 2 of 60 on a first pass; retrying on
  a fresh session cleared both, and a 155-page run afterwards saw 0 failures. If you see the rate
  climb, switch `proxyConfiguration` to RESIDENTIAL.
- **Currency is not converted.** `soldPrice` is in `currency` as BaT published it. BaT is
  US-centric but not US-only: on the 400-row live run 398 rows were in USD and 2 in EUR, and 11 of
  400 cars were outside the United States.
- **A sale with no readable price is not delivered and not charged.** On a price database the price
  is the record, so a row BaT reports as sold that carries no number is treated as a parse failure
  rather than shipped with a null — it is listed by URL in
  `RUN_SUMMARY.soldRowsWithNoPriceNotDelivered` so the gap is visible instead of averaging into
  your comps. On the verification runs this list was empty.
- Rating and last-updated for competing Actors are not in the Apify API; the comparison table above
  uses pricing, usage and run-success figures that are.

***

### ⚖️ Source and legal

Data comes from two public surfaces on `bringatrailer.com`:

1. `wp-json/bringatrailer/1.0/data/listings-filter` — the same endpoint the site's own
   **Auctions → Results** page calls to render itself.
2. The public listing pages at `/listing/<slug>/`.

No login, no account, no private data, no bidding, no messaging.

`bringatrailer.com/robots.txt`, quoted verbatim as served on 2026-09-18:

```
User-agent: *
Crawl-delay: 1
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php
Disallow: /message/
Disallow: /listing/*/carfax
Disallow: /member/
Disallow: /*/contact/
Disallow: /*/feed/
Disallow: /account/
Disallow: /?q=
Disallow: /search/
Disallow: /*corder=
Disallow: /*utm_source=
```

Neither `/wp-json/...` nor `/listing/<slug>/` is disallowed. This Actor does not request any of the
disallowed paths — in particular it never opens `/member/` profile pages or `/listing/*/carfax`,
even though a seller handle links to one.

You are responsible for how you use the output. Bring a Trailer's own Terms of Use govern your use
of their site, and nothing here is legal advice.

# Actor input Schema

## `result` (type: `string`):

Which completed auctions to return. "Sold" (the default) returns only listings with a realized sale price - 209,076 of them. "Reserve not met" returns the 54,848 that ended below reserve, where there is a high bid but no sale. "All results" returns both. This is BaT's own server-side facet, not a filter applied after download. Live auctions still in progress are deliberately out of scope - this Actor is a price database.

## `searchTerms` (type: `array`):

Words that must appear in the listing, e.g. "porsche 911" or "land cruiser fj40". All the terms you enter are sent to BaT as a single search phrase. Leave empty to sweep without a keyword.

## `excludeTerms` (type: `string`):

A word that must NOT appear in the listing, e.g. "project" or "parts". BaT applies this itself, so excluded listings are never downloaded and never charged.

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

Paste BaT URLs instead of building a filter. Three kinds work: a model or make page such as https://bringatrailer.com/porsche/911/ (the Actor reads the curated listing set off the page itself); a results page with its filters already set, such as https://bringatrailer.com/auctions/results/?search=e30+m3\&result=sold\&yearFrom=1988 (every URL parameter is translated); and a single listing such as https://bringatrailer.com/listing/1995-ferrari-456-gt-48/. Anything else is reported by name rather than silently ignored.

## `minYear` (type: `integer`):

Earliest model year to include. BaT indexes a model year for 200,070 of its 209,076 sold listings (95.7%, measured across every year from 1900 to 2026); parts, wheels and a few other lots carry no year and are excluded by any year filter.

## `maxYear` (type: `integer`):

Latest model year to include. Leave both year fields empty for no year filter.

## `eras` (type: `array`):

BaT's own decade tags. Coarser than the model-year range above, and independent of it. Leave empty for every era.

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

BaT's own category tags. Only about two thirds of listings carry one, so this filter is narrower than it looks - a 1995 Ferrari with no category tag will not match "Convertibles" even if it is one. Leave empty for every category.

## `origins` (type: `array`):

Where the vehicle was built, as BaT tags it. "Prewar" is an origin on BaT rather than an era. Leave empty for every origin.

## `locations` (type: `array`):

Where the vehicle is, by country, plus BaT's three region groups (North America, Europe, Australia/New Zealand). Picking a group selects every country in it. Leave empty for everywhere.

## `listingTypes` (type: `array`):

Keep only listings BaT flags this way. "No Reserve" sold without a reserve price, "Premium" is BaT's paid placement, and "BaT Alumni" means the same vehicle has been sold on BaT before - which is what makes a repeat-sale price series possible.

## `maxPriceUsd` (type: `integer`):

Keep only results at or below this amount. BaT applies it server-side, so dearer cars are never downloaded and never charged. Leave empty for no ceiling.

## `soldWithin` (type: `string`):

Restrict to auctions that closed recently. Measured on 2026-09-18: 1,005 sold in the last 7 days, 4,278 in the last 30, 43,569 in the last year. Leave on "All time" for the full archive.

## `maxResults` (type: `integer`):

Hard cap on rows delivered, and therefore on what you are charged: the cap is counted against rows PRODUCED, so a cap of 60 charges exactly 60. Set 0 for no cap - BaT publishes 209,076 sold results, so read the price first.

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

ON (the default) opens every listing to read the chassis/VIN number, lot number, city and postal code, seller handle and type, winning bidder handle, bid count, odometer, engine, transmission, colours, the full Listing Details bullet list, view and watcher counts and the photo count. Measured chassis fill with this on: 153 of 154 sampled listings (99.4%); the one miss was a set of wheels. OFF skips those fields and runs about eight times faster - useful when you only want prices and dates. The per-row price is the same either way.

## `includePhotos` (type: `boolean`):

Adds an imageUrls column with the full-resolution gallery. Measured on a 400-row live run: the median BaT listing carries 151 photos, so this makes the dataset several times larger. Needs "Open each listing page".

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

Adds a description column with the listing write-up. The shorter excerpt is always included. Needs "Open each listing page".

## `dedupeByChassis` (type: `boolean`):

Keeps only the first listing seen for each chassis/VIN, which collapses BaT Alumni repeat sales of the same vehicle into one comp. Note this is the only filter here that cannot be applied before download: the chassis number is on the listing page, so a row dropped this way has already cost a page fetch (it is not charged - you are only charged for rows delivered).

## `autoPartition` (type: `boolean`):

BaT's results search returns at most about 10,000 rows for any one query and then silently answers "0 results" - so a naive full sweep of the 209,076 sold listings stops at under 4% while reporting success. With this ON the Actor reads both ends of a result set (19,920 rows) and splits anything larger by model year, then category, then origin, deduplicating by listing id. Leave it on unless you specifically want one window of results. Either way RUN\_SUMMARY records exactly how many rows BaT said were there against how many were read.

## `sort` (type: `string`):

Which end of the result set to read first. Recently closed and Oldest first are the two orders that can be read from both ends, so they are the only ones that reach past BaT's 10,000-row window without partitioning; Most viewed and Highest bid have no reverse and are capped at one window per query.

## `detailConcurrency` (type: `integer`):

How many listing pages to open at once. Measured on 2026-09-18: 155 pages at 10 in parallel took 31 seconds with zero failures. Lower it if you see repeated retries.

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

Apify Proxy is on by default and the datacenter pool is enough: measured 2026-09-18, 155 listing pages returned 155 x HTTP 200 through it. BaT does occasionally answer 403 to a datacenter IP (2 of 60 on a first pass) and the Actor retries those on a fresh session. Switch to RESIDENTIAL if you see that rate climb.

## Actor input object example

```json
{
  "result": "sold",
  "searchTerms": [],
  "excludeTerms": "",
  "startUrls": [],
  "eras": [],
  "categories": [],
  "origins": [],
  "locations": [],
  "listingTypes": [],
  "soldWithin": "",
  "maxResults": 25,
  "fetchDetails": true,
  "includePhotos": false,
  "includeDescription": false,
  "dedupeByChassis": false,
  "autoPartition": true,
  "sort": "td",
  "detailConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One completed Bring a Trailer auction per row: chassis/VIN number, realized sale price, sale date, lot number, city, state and postal code, seller handle and whether they are a private party or a dealer, the winning bidder's handle, bid count, odometer with its unit, engine, transmission, colours, the full Listing Details list, BaT's own make/model/era/origin/category tags, view and watcher counts, photo count and the listing URL.

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

RUN\_SUMMARY: how many results Bring a Trailer said were there against how many were actually read, every partition walked and any that hit BaT's 10,000-row search window, the per-field fill percentages COUNTED on this run's own rows, rows delivered (== rows charged), and three separate lists for the three kinds of nothing - listings that do not exist, listings unreachable after retries on fresh proxy sessions, and rows with no usable id. None of the three is charged.

# 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 = {
    "result": "sold",
    "searchTerms": [],
    "maxResults": 25,
    "fetchDetails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/bringatrailer-results-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 = {
    "result": "sold",
    "searchTerms": [],
    "maxResults": 25,
    "fetchDetails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/bringatrailer-results-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 '{
  "result": "sold",
  "searchTerms": [],
  "maxResults": 25,
  "fetchDetails": true
}' |
apify call scrapersdelight/bringatrailer-results-scraper --silent --output-dataset

```

## MCP server setup

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