# The RealReal Scraper - Luxury Prices, Brands & Listings (`abotapi/therealreal-scraper`) Actor

Scrape luxury consignment listings from therealreal.com. Browse by designer, category or keyword and get designer, item class, condition grade, retail price versus current list price, measurements, materials and authentication notes. Built for price positioning per brand per category.

- **URL**: https://apify.com/abotapi/therealreal-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce
- **Features:** Limited permissions
- **Stats:** 10 total users, 3 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 item results

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

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

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

## The RealReal Scraper: Consigned Luxury Items, Prices and Condition Grades

The RealReal Scraper turns The RealReal's live consignment catalogue into clean tabular data. Every row is one physical, one-of-a-kind item: the designer, the item class, the marketplace's own condition grade, what the piece originally retailed for and what it is consigned for today, with both discount percentages already computed. Browse by category or by designer, or paste category, designer or item links directly, then export to JSON, CSV or Excel, or read the dataset through the API.

### Why This Scraper?

- **Two real browse axes.** Category paths and designer names are both applied by the marketplace's own server, so a per-designer pull returns that brand's current catalogue with no further filtering needed. There is no keyword box and no sort option is offered, because both are accepted by this site and then silently ignored on the pages this Actor reads, so this Actor skips a pasted keyword link with an explanation instead of quietly answering it with the wrong data.
- **A price-positioning table, with no extra charge.** Every row carries `retailPrice`, `listPrice`, `originalPrice`, `discountPct` and `vsRetailPct`, so a pull for one designer in one category is ready for a spreadsheet without any joining.
- **Built for a consignment marketplace, not a retail catalogue.** Each row is a single physical item, not a restocked SKU. Sold items are returned by default because a sold item's final price is a real comparable, and the condition grade is returned exactly as the marketplace assigns it, with the inspector's own free-text note kept in a separate field.
- **Cheap by default.** Designer, condition grade, all three prices, colour, primary material and the image gallery all come from the result pages at no extra cost. Reading each item's own page for measurements, full copy, materials and the authentication note is optional and billed only for items that actually get it.
- **Incremental mode for recurring monitoring.** Schedule the same search and get back only new and updated items by default (`NEW`, `UPDATED` with the changed fields named, `UNCHANGED` suppressed); turn on Emit expired items too, once a run scans the whole scope end to end, to also get `EXPIRED` items (which on a consignment marketplace usually means sold) and, on a later run, `REAPPEARED` ones.
- **Resume for one interrupted run.** Paste a run or dataset id and the walk skips everything that run already collected, without re-paying for it, and still advances past a page made entirely of already-known items instead of mistaking that for the site repeating itself.
- **A run that could read nothing fails instead of returning empty.** If this run returned no items and at least one page was refused, it stops with an error instead of quietly reporting an empty catalogue, so "nothing matched" is never confused with "the connection could not read here"; a run that read at least something still succeeds even if part of it was refused.

### Use Cases

- **Brand and merchandising teams:** track how a designer's pieces are pricing on the resale market, category by category, against what they retailed for.
- **Resellers and consignors:** benchmark current listing prices for a brand or item class before pricing their own inventory.
- **Appraisers and insurers:** pull comparable list and sold prices for a specific designer, item class or condition grade.
- **Market researchers:** schedule the same search with incremental mode on to build a time series of markdowns and sell-through.

### Data You Get

> Sample shape: values are entirely invented placeholders for illustration, not a real listing.

| Field | Example |
| --- | --- |
| `itemId` | `"00000000"` |
| `sku` | `"SAM000000"` |
| `name` | `"Sample Leather Tote"` |
| `url` | `"https://www.therealreal.com/products/women/handbags/totes/sample-item-000000"` |
| `designer` | `"Example Maison"` |
| `designerId` | `"0"` |
| `itemClass` | `["Women", "Handbags", "Totes"]` |
| `category` | `"Totes"` |
| `condition` | `"Very Good"` |
| `conditionNote` | `"Light scuffing at the base corners, noted by our inspection team."` |
| `color` | `"Brown"` |
| `primaryMaterial` | `"Leather"` |
| `retailPrice` | `3800.0` |
| `listPrice` | `990.0` |
| `originalPrice` | `1600.0` |
| `discountPct` | `38.1` |
| `vsRetailPct` | `73.9` |
| `isOnSale` | `true` |
| `availability` | `"AVAILABLE"` |
| `isSold` | `false` |
| `images` | `["https://product-images.therealreal.com/SAM000000_1_enlarged.jpg"]` |
| `detailFetched` | `true` |

Every row also carries `variantId`, `gender`, `artist` (for art and collectibles), `currency`, `attributes` (the marketplace's own attribute list), `quantity`, `obsessionCount`, `isEditorsPick`, `badge`, `imageCount`, `sourceUrl` and `scrapedAt`. Turning on **Read each item's page** adds `measurements`, `materials`, `description` and `authenticationNote`, plus `sustainability` (carbon and water figures); `returnPolicy` and `disclaimers` (the item page's return terms and notices) are filled on item-page rows. Every row also carries `slug` (the last part of the item link). `taxons` (id, name and permalink of each marketplace category node), `valuationReportUrl` and `measurementGuide` come from the item page, so they are filled on item-page rows only (**Read each item's page** on, or pasted item links) and are null on result-card rows, and also where the marketplace publishes nothing for the item. Turning on **Incremental changes for scheduled runs** adds `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt` to every row.

### How to Use

1. Pick a **mode**: `search` to browse by category or designer, or `url` to scrape pasted The RealReal links.
2. In search mode, fill in **Category paths** and/or **Designers** (optionally narrowed with **Category for the designers above**), plus any of the filters below. In url mode, paste category, designer or item links.
3. Turn on **Read each item's page** only when you need measurements, the full item copy or the authentication note; leave it off for the cheap mode, which still returns designer, condition, both prices and both discount percentages.
4. Set **Max items** to control run size and cost, then click **Start**.

**Pull one designer's handbags for price positioning:**

```json
{
  "mode": "search",
  "designers": ["Chanel"],
  "designerCategory": "women/handbags",
  "maxItems": 200
}
```

**Browse a category with condition and price filters:**

```json
{
  "mode": "search",
  "categories": ["women/handbags"],
  "condition": "Excellent",
  "minPrice": 500,
  "maxPrice": 2000,
  "maxItems": 100
}
```

**Paste links directly, with full item detail:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.therealreal.com/shop/women/handbags",
    "https://www.therealreal.com/designers/chanel/women/handbags"
  ],
  "fetchDetails": true
}
```

**Recurring monitoring of one designer's category (narrowed to stay under the 2000-item depth limit):**

```json
{
  "mode": "search",
  "designers": ["Hermes"],
  "designerCategory": "women/handbags",
  "incrementalMode": true,
  "emitExpired": true,
  "maxItems": 0
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/therealreal-scraper").call(
    run_input={"mode": "search", "designers": ["Chanel"], "maxItems": 50})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["designer"], item["name"], item["listPrice"], item["vsRetailPct"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/therealreal-scraper').call({
  mode: 'search', designers: ['Chanel'], maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Dataset views

The Output tab offers three views of the same rows: **Consigned items** (prices, condition and discounts), **Item details** (measurements, materials, copy and authentication note) and **Changes** (`changeType`, `changedFields`, first and last seen, for incremental runs).

#### URL mode: supported link shapes

| Link shape | Example | What you get |
|---|---|---|
| Category page | `therealreal.com/shop/women/handbags` | That category's result pages |
| Top-level category | `therealreal.com/shop/jewelry` | That category's result pages |
| Designer + category | `therealreal.com/designers/chanel/women/handbags` | That designer's items in the category |
| Bare designer | `therealreal.com/designers/chanel` | That designer's whole catalogue |
| Paginated link | `therealreal.com/shop/women/handbags?page=2` | The marketplace ignores `page=N` and serves page one, so this returns the same items as the plain category link |
| Price-filtered | `therealreal.com/shop/women/handbags?price_from=100&price_to=2000` | Items in that category within the pasted price band (use the site's own `price_from` / `price_to` link parameters) |
| Item page | `therealreal.com/products/women/handbags/bucket-bags/celine-canvas-plein-soleil-wyo9d` | That one item with full detail |
| Keyword search | `therealreal.com/shop?keywords=chanel` | Skipped with a warning, because the marketplace does not apply keywords (the example link is in the default list, so an untouched run prints that warning once) |

**Behaviour change:** in url mode, leaving the **links** field untouched now scrapes one example of each supported shape above (the keyword link is skipped with a warning) instead of nothing. **Max items** (default 20, at most 100000) still caps the run, so lower it or replace the links with your own.

#### Why there is no keyword box

This marketplace does not apply a keyword to the pages this Actor reads: a keyword is accepted and then ignored, and the page comes back with the entire catalogue, about 3 million items, whose results have nothing to do with what was asked for. This Actor skips a pasted keyword link with an explanation rather than answering it with the wrong data. Browse by category or designer instead, and narrow with the filters above.

#### The marketplace's own depth limit

This site serves at most 2000 items per search, however many actually match, and the run's log says so when a search hits that limit. To collect more than 2000 items on one topic, split the search: browse one designer at a time, narrow to a single category, or walk the catalogue in price bands with **Minimum list price** and **Maximum list price**. Each of those is a separate search with its own 2000-item window.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset id and the walk skips everything already collected there. It still advances correctly past a page made entirely of already-known items, rather than mistaking a page of only-old items for the site repeating itself.
- **Incremental mode** (`incrementalMode`) is for running the same search again and again, for example daily. Every item is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless **Emit unchanged items** is on), or `EXPIRED` (only returned with **Emit expired items** on). `EXPIRED` is only ever produced after a run whose scan of the tracked search was complete: not capped by Max items, not resumed, not stopped at the marketplace's 2000-item depth limit. `REAPPEARED` can only ever follow an item that a previous run already tombstoned as `EXPIRED` that way; it never fires on a run's first sight of an item. Change detection compares the whole row: price and markdowns, condition, availability, title, photos, badges and item-page copy all drive `UPDATED`, with `changedFields` naming what moved. `scrapedAt`, `obsessionCount` (other shoppers' favourites) and `detailFetched` are never compared. Use **Ignore fields when detecting changes** (`ignoreFieldsForChanges`) to leave more fields out; changing it never causes a one-time wave of `UPDATED` rows, because the saved baseline is re-compared under the current setting. With incremental mode off, output is exactly as it always was.

### Send results into your apps (MCP connectors)

Optionally pipe results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape; the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable summary of each item, not the full record. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

1. Authorize a connector once under **Apify, Settings, API & Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in **Pipe results into your apps**. If the picker is empty, no connector has been authorized yet.
3. For Notion, also set **Notion parent page** to the page under which item pages should be created.

The connection is mediated by Apify's own MCP proxy, so this Actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` to browse by category or designer, or `url` to scrape pasted links. |
| `categories` | array | (none; prefill `["women/handbags"]`) | Category paths, search mode only, for example `women/handbags` or `men/shoes`. |
| `designers` | array | (none) | Designer names, search mode only, for example `Chanel` or `Bottega Veneta`. |
| `designerCategory` | string | (none) | Optional, narrows every designer above to one category path. |
| `urls` | array | one example link per supported shape (see URL mode above) | The RealReal links, url mode only: item, category or designer links. |
| `designer` | string | (none) | Optional filter: keep only this designer's items. |
| `condition` | string | `""` (Any) | Optional filter: keep only one condition grade. |
| `color` | string | (none) | Optional filter: keep only items in this colour. |
| `minPrice` | integer | (none) | Optional. Keep only items whose current list price is at or above this amount, in USD. |
| `maxPrice` | integer | (none) | Optional. Keep only items whose current list price is at or below this amount, in USD. |
| `availableOnly` | boolean | `false` | Keep only items that can be bought right now; off by default, so sold items are included. |
| `onSaleOnly` | boolean | `false` | Keep only items whose list price is below their earlier consignment price. |
| `vintageOnly` | boolean | `false` | Keep only items the marketplace flags as vintage. |
| `editorsPickOnly` | boolean | `false` | Keep only items the marketplace's own editors have highlighted. |
| `withTagsOnly` | boolean | `false` | Keep only items still carrying their original retail tags. |
| `fetchDetails` | boolean | `false` | Read each item's own page for measurements, full copy, materials and the authentication note. Billed per item that actually gets it; off by default. |
| `maxItems` | integer | `20` | Stop after this many items across the whole run. `0` = unlimited. |
| `maxPages` | integer | `0` | Stop after this many result pages per search. `0` = unlimited (bounded instead by Max items, the marketplace's own depth limit, or a natural end of results). |
| `resumeFromRunId` | string | (none) | Continue one interrupted run: paste its run or dataset id. |
| `incrementalMode` | boolean | `false` | Turn on for recurring monitoring of the same search. |
| `stateKey` | string | (none) | Name or share an incremental-mode monitoring campaign. |
| `emitUnchanged` | boolean | `false` | Also return, and bill, items unchanged since the last run (incremental mode only). |
| `emitExpired` | boolean | `false` | Also return, and bill, items no longer listed since the last run (incremental mode only). |
| `ignoreFieldsForChanges` | array | `[]` | Ignore fields when detecting changes (incremental mode only): field names, or dotted paths for nested fields, left out of the comparison and out of `changedFields` but still returned in the row. |
| `proxy` | object | `{"useApifyProxy": true}` | Connection settings. The run escalates to the enhanced unblocking connection automatically, once, when the marketplace refuses pages on the default connection (currently every page type), and stays on it for the rest of that run. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each item to apps authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are placeholders, not a real listing.

```json
{
  "itemId": "00000000",
  "sku": "SAM000000",
  "variantId": null,
  "name": "Sample Leather Tote",
  "url": "https://www.therealreal.com/products/women/handbags/totes/sample-item-000000",
  "designer": "Example Maison",
  "designerId": "0",
  "itemClass": ["Women", "Handbags", "Totes"],
  "category": "Totes",
  "condition": "Very Good",
  "conditionNote": "Light scuffing at the base corners, noted by our inspection team.",
  "color": "Brown",
  "gender": "Women",
  "primaryMaterial": "Leather",
  "attributes": { "Color": ["Brown"], "Primary Material": ["Leather"], "Gender": ["Women"], "Condition": ["Light scuffing at the base corners, noted by our inspection team."] },
  "retailPrice": 3800.0,
  "listPrice": 990.0,
  "originalPrice": 1600.0,
  "currency": "USD",
  "discountPct": 38.1,
  "vsRetailPct": 73.9,
  "isOnSale": true,
  "availability": "AVAILABLE",
  "isSold": false,
  "quantity": "1",
  "obsessionCount": 3,
  "isEditorsPick": false,
  "badge": null,
  "images": ["https://product-images.therealreal.com/SAM000000_1_enlarged.jpg"],
  "imageCount": 1,
  "sourceUrl": "https://www.therealreal.com/shop/women/handbags",
  "detailFetched": true,
  "measurements": { "Height": "10\"", "Width": "14\"", "Handle Drop": "9\"" },
  "materials": ["Sample Leather Tote", "Brown Leather", "Silver-Tone Hardware", "Double Top Handles", "Magnetic Closure"],
  "description": "Sample Leather Tote. Brown Leather. Silver-Tone Hardware. Double Top Handles. Magnetic Closure.",
  "authenticationNote": "Authenticated by our in-house specialists prior to listing.",
  "sustainability": { "kilogramsOfCarbon": "2.4", "litersOfWater": "410.0" },
  "returnPolicy": "Final Sale",
  "disclaimers": ["Vintage items are sold as-is and may show signs of age consistent with the condition grade."],
  "scrapedAt": "2026-09-11T00:00:00Z"
}
```

### Plan Requirement

This marketplace sits behind a bot-protection wall that currently refuses ordinary and residential connections on every page type (catalogue, designer and item pages alike), so the run does not work from the default connection alone. When the marketplace refuses pages on your configured connection, the run switches once to Apify Proxy's enhanced unblocking group, which currently reads every page type, and stays on it for the rest of that run. That group is billed on your own Apify plan and is more expensive per page than a standard connection, so expect it on essentially every run. It needs a paid Apify plan: on a plan without proxy access the run stops with "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required". It never falls back to the run's own address. Picking a connection group yourself adds cost without adding results, so leave the group list empty. If the log shows "Served over the backup connection (the default connection was unavailable)", that is the switch described here, not a fault. If no page could be loaded at all, the run fails with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."

### FAQ

#### How much does it cost?

Three charge events. **Actor start** is charged when the run starts, scaled by the run's memory. **Item result** is charged once per item returned in the dataset, and is the main cost. **Item page detail** is charged once per item that both reached the dataset and had its own page actually read: an item whose page could not be read is still returned with its result-page fields and is NOT charged this event, and an item suppressed as unchanged by incremental mode is never charged any event. This surcharge only applies when **Read each item's page** is switched on (it is off by default), with one exception: a pasted item link always reads the item's own page, because there is no result card behind it to fall back to, so whenever it reaches the dataset it is billed this event whatever the toggle says. The **Pricing** tab shows current rates; use **Max items** to cap any run's cost.

#### Is it legal to scrape The RealReal?

This Actor collects only publicly available consignment listing data. You are responsible for how you use it: follow The RealReal's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices, condition grades and item details are generally facts, but item photography may be subject to third-party rights.

#### Can I get only new, changed or sold items on a schedule?

Yes. Schedule the Actor from the **Schedules** tab and turn on **Incremental changes for scheduled runs**. Each run then returns only new and updated items by default, and unchanged items are not returned or billed unless you ask for them; turn on **Emit expired items** too, once a run scans the whole scope end to end, to also get expired items and, on a later run, reappeared ones.

#### Why did my run fail instead of returning an empty dataset?

If this run returned no items and at least one page was refused, it stops with an error so "nothing matched" is never confused with "the connection could not read here". Run it again in a few minutes, or check the Connection section. If some pages could not be read but items were still returned, the run still succeeds with a note on the run explaining the shortfall.

#### Why did my search stop at 2000 items even though the site reports far more matches?

This marketplace clamps every single search at 2000 items, however many it reports matching. Split a large category into one designer at a time, a single category, or price bands, and run each as its own search.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push item summaries into Notion, Linear or Airtable.

### 🔗 Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/ssense-product-scraper"><b>SSENSE Scraper</b></a><br>Scrape SSENSE by keyword, category, designer or URL. Every row carries the selling price...</td><td>🧩 <a href="https://apify.com/abotapi/joinswitch-rental-scraper"><b>Switch Scraper</b></a><br>Scrape Switch designer handbag and jewelry rental listings by keyword, filter or URL...</td></tr>
<tr><td>👗 <a href="https://apify.com/abotapi/24s-com-scraper"><b>24S Scraper</b></a><br>Scrape 24S luxury fashion by category, filters, or URL. Extract brand, name, price...</td><td>🏷️ <a href="https://apify.com/abotapi/chrono24-scraper"><b>Chrono24 Scraper</b></a><br>Scrape Chrono24 luxury watch listings by keyword, filters or URL. Extract 50+ fields...</td></tr>
<tr><td>🏷️ <a href="https://apify.com/abotapi/thredup-scraper"><b>ThredUp Scraper</b></a><br>Scrape ThredUp resale listings by keyword, department, brand, size, condition, price or...</td><td>🧩 <a href="https://apify.com/abotapi/grailed-marketplace-scraper"><b>Grailed Scraper</b></a><br>Scrape Grailed by keyword, category, designer, condition, size or URL. Extract prices and...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/therealreal-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Choose 'search' to browse by category or designer, or 'url' to scrape pasted The RealReal links. Each mode reads only its own fields below.

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

Only used when mode = search, ignored in URL mode. Category paths as they appear in a The RealReal URL after /shop/, for example 'women/handbags', 'men/shoes', 'jewelry' or 'watches'. Each is walked as its own catalogue.

## `designers` (type: `array`):

Only used when mode = search. Designer names, for example 'Chanel', 'Louis Vuitton' or 'Bottega Veneta'. Each designer is walked as its own catalogue, which is what makes a per-brand price-positioning pull a single run. Names are turned into the designer's own catalogue page, so ordinary spelling works.

## `designerCategory` (type: `string`):

Optional, and only used alongside Designers. Narrows every designer catalogue to one category path, for example 'women/handbags' or 'men/shoes'. The marketplace does not serve a designer's whole catalogue on one page, so when this is empty each designer is walked under 'women'.

## `urls` (type: `array`):

Only used when mode = url, ignored in search mode. Item links under /products/, category links under /shop/ and designer links under /designers/ are all accepted. A designer link with no category is routed to that designer's catalogue, because the marketplace does not serve the bare designer landing page to this Actor. A keyword link is skipped and says why. Left untouched, url mode scrapes one example of each supported link shape (still capped by Max items).

## `designer` (type: `string`):

Optional. Keep only items by this designer, for example 'Chanel', 'Hermes' or 'Louis Vuitton'. To pull several designers as separate catalogues, use the Designers list above instead.

## `condition` (type: `string`):

Optional. Keep only items at this condition grade. The marketplace grades every consigned item on its own five-step scale, from Pristine (original condition, may include tags and box) down to Fair (heavily worn).

## `color` (type: `string`):

Optional. Keep only items in this colour, as the marketplace names it, for example 'Black', 'Brown', 'Blue' or 'Gold'.

## `minPrice` (type: `integer`):

Optional. Only keep items whose current list price is at or above this amount, in US dollars. Also sent to the marketplace as a filter, so it narrows the walk itself rather than only the output. Useful for walking a large catalogue in price bands.

## `maxPrice` (type: `integer`):

Optional. Only keep items whose current list price is at or below this amount, in US dollars. Also sent to the marketplace as a filter.

## `availableOnly` (type: `boolean`):

Optional. Keep only items that can be bought right now. Sold items are returned by default, because on a consignment marketplace a sold item and its final price are the most useful data points for pricing analysis.

## `onSaleOnly` (type: `boolean`):

Optional. Keep only items whose current list price is below their earlier consignment price.

## `vintageOnly` (type: `boolean`):

Optional. Keep only items the marketplace flags as vintage.

## `editorsPickOnly` (type: `boolean`):

Optional. Keep only items the marketplace's own editors have highlighted.

## `withTagsOnly` (type: `boolean`):

Optional. Keep only items still carrying their original retail tags, which usually sit at the top of the condition scale.

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

Read every item's own page for its measurements (height, width, strap drop and so on), full description and materials, item-class hierarchy, authentication note, return policy and carbon and water figures. Off by default because it costs one extra page per item. A pasted item link always reads the item page, because there is no result card behind it, so an item link is always billed one 'Item page detail' event whatever this toggle says.

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

Maximum number of items to return across the whole run. This is the run's cap. Use 0 for unlimited.

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

Maximum number of result pages to take per keyword, category, designer or link. Each page carries about 120 items. 0 = unlimited; the run then stops only at Max items, at the marketplace's own per-search depth limit, or when there are no more results.

## `proxy` (type: `object`):

Connection used for every request. Leave the default; the run switches connections on its own when the marketplace refuses a page.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large catalogue pull without returning or charging for items already collected there. Use this after an interrupted run. For recurring monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns every matching item as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED items, which makes it a cheap way to watch markdowns and new consignments. Turn on 'Emit unchanged' or 'Emit expired' only when you also want those rows returned (and billed). Change detection compares the whole item row, so a price or markdown, condition, availability, title, photos, badges or item-page copy change makes it UPDATED (changedFields names what moved). Never compared: scrapedAt, obsessionCount (other shoppers' favourites), detailFetched, and anything you list in the ignore field below. State is kept separately for each search and filter setup; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to derive a key automatically from the search and filter settings, so two different searches never mix state.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return items that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you want a full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return items that were present in a previous run but are no longer listed, marked EXPIRED. On a consignment marketplace that usually means the item sold, which makes it the most useful signal here. Only produced once a run has fully scanned the tracked search, never when Max items capped it, when Resume was used, or when the run hit the marketplace's per-search depth limit. This returns, and bills, extra rows.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names that must NOT make an item count as UPDATED, for example listPrice if you only care about availability. Use a dotted path for a nested field (sustainability.carbon). The fields stay in the output; they are only left out of the comparison and out of changedFields. Takes effect immediately without an UPDATED wave.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per item; other connectors get a best-effort write or digest. Each connector receives a condensed summary per item, not the full record; the complete record always stays in the dataset. Leave empty to skip; this never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "categories": [
    "women/handbags"
  ],
  "designers": [],
  "urls": [
    "https://www.therealreal.com/shop/women/handbags",
    "https://www.therealreal.com/shop/jewelry",
    "https://www.therealreal.com/shop/women/handbags/crossbody-bags",
    "https://www.therealreal.com/designers/chanel/women/handbags",
    "https://www.therealreal.com/designers/chanel",
    "https://www.therealreal.com/shop/women/handbags?page=2",
    "https://www.therealreal.com/shop/women/handbags?price_from=100&price_to=2000",
    "https://www.therealreal.com/products/women/handbags/bucket-bags/celine-canvas-plein-soleil-wyo9d",
    "https://www.therealreal.com/shop?keywords=chanel"
  ],
  "condition": "",
  "availableOnly": false,
  "onSaleOnly": false,
  "vintageOnly": false,
  "editorsPickOnly": false,
  "withTagsOnly": false,
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (type: `string`):

No description

# 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 = {
    "mode": "search",
    "categories": [
        "women/handbags"
    ],
    "designers": [],
    "urls": [
        "https://www.therealreal.com/shop/women/handbags",
        "https://www.therealreal.com/shop/jewelry",
        "https://www.therealreal.com/shop/women/handbags/crossbody-bags",
        "https://www.therealreal.com/designers/chanel/women/handbags",
        "https://www.therealreal.com/designers/chanel",
        "https://www.therealreal.com/shop/women/handbags?page=2",
        "https://www.therealreal.com/shop/women/handbags?price_from=100&price_to=2000",
        "https://www.therealreal.com/products/women/handbags/bucket-bags/celine-canvas-plein-soleil-wyo9d",
        "https://www.therealreal.com/shop?keywords=chanel"
    ],
    "availableOnly": false,
    "onSaleOnly": false,
    "vintageOnly": false,
    "editorsPickOnly": false,
    "withTagsOnly": false,
    "fetchDetails": false,
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/therealreal-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 = {
    "mode": "search",
    "categories": ["women/handbags"],
    "designers": [],
    "urls": [
        "https://www.therealreal.com/shop/women/handbags",
        "https://www.therealreal.com/shop/jewelry",
        "https://www.therealreal.com/shop/women/handbags/crossbody-bags",
        "https://www.therealreal.com/designers/chanel/women/handbags",
        "https://www.therealreal.com/designers/chanel",
        "https://www.therealreal.com/shop/women/handbags?page=2",
        "https://www.therealreal.com/shop/women/handbags?price_from=100&price_to=2000",
        "https://www.therealreal.com/products/women/handbags/bucket-bags/celine-canvas-plein-soleil-wyo9d",
        "https://www.therealreal.com/shop?keywords=chanel",
    ],
    "availableOnly": False,
    "onSaleOnly": False,
    "vintageOnly": False,
    "editorsPickOnly": False,
    "withTagsOnly": False,
    "fetchDetails": False,
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/therealreal-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 '{
  "mode": "search",
  "categories": [
    "women/handbags"
  ],
  "designers": [],
  "urls": [
    "https://www.therealreal.com/shop/women/handbags",
    "https://www.therealreal.com/shop/jewelry",
    "https://www.therealreal.com/shop/women/handbags/crossbody-bags",
    "https://www.therealreal.com/designers/chanel/women/handbags",
    "https://www.therealreal.com/designers/chanel",
    "https://www.therealreal.com/shop/women/handbags?page=2",
    "https://www.therealreal.com/shop/women/handbags?price_from=100&price_to=2000",
    "https://www.therealreal.com/products/women/handbags/bucket-bags/celine-canvas-plein-soleil-wyo9d",
    "https://www.therealreal.com/shop?keywords=chanel"
  ],
  "availableOnly": false,
  "onSaleOnly": false,
  "vintageOnly": false,
  "editorsPickOnly": false,
  "withTagsOnly": false,
  "fetchDetails": false,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/therealreal-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/therealreal-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/0oGvEM1cXCSNnqSYM/builds/j3mZVtmcO2ztXMsOk/openapi.json
