# Facebook Marketplace Scraper with Sold Tracker (`automation_craft/facebook-marketplace-scraper`) Actor

Facebook Marketplace scraper, no login: search by words, city, ZIP or category, browse a city, or paste links. Rows carry price, place, date, photo and seller; add details, vehicle and rental facts, a sold tracker for price drops, a memory for new listings and alerts.

- **URL**: https://apify.com/automation_craft/facebook-marketplace-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** E-commerce, Automation, Real estate
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.38 / 1,000 listings

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

### Facebook Marketplace Scraper: Listings, Sold Tracker

**Facebook Marketplace scraper** without a login: search listings by words, city, postal code or category, browse a whole city, a wide area or every city of a country, or paste Marketplace links, and get one row per listing with its title, price, place, photo, listing date, delivery options and seller name. Add each listing's details (description, every photo, condition, coordinates, vehicle and rental facts) when you need them. A memory gives you only new or changed listings on a schedule, and the **sold tracker** tells you, run after run, which listings you watch were sold, went pending, were removed or changed their price.

#### Quick start

1. Type what you are looking for in **Search words** (`iphone 15`, `sofa`, `honda civic`), or pick **Categories** (vehicles, property rentals, electronics, home, free stuff and more), or both.
2. Add one or more **Places**: as Facebook names them (`Austin, Texas`, `London, United Kingdom`), as coordinates (`30.2672,-97.7431`), as a postal code (`10001`, `10115, Germany`, `M5V, Canada`) or as a Marketplace city slug or location id (`nyc`, `108424279189115`). Set a **Search area radius** up to 500 km to cover a region, or a **Country preset** to cover every Marketplace city of a country, or paste **Marketplace links** instead.
3. Set **Listings per search** (50 when left out) and any filter: price, listed in the last N days or after a date, pickup or shipping, condition, sold only, vehicle make, transmission, seller, year and mileage.
4. Run. Download the rows as JSON, CSV or Excel, or read them through the API.

To get only new listings, set a **Memory name** and schedule the run. To follow listings you already know (your own, a competitor's stock, a car you want), paste their ids or links into **Sold tracker** and schedule it.

### What it does

| Path | How | What you get |
|---|---|---|
| Search | words in places (names, coordinates, postal codes, city slugs, location ids) | listing rows in Facebook's order, 11 sort orders |
| Browse | a place or a category without words | the place's or category's listings, newest first or a browse order (oldest, farthest, lowest or highest mileage, newest or oldest model year) |
| Categories | 17 Marketplace categories, with or without words | each category is its own search in every place |
| Vehicles | the vehicles category, or a make (65 makes), transmission, private or dealer seller, year and mileage | vehicle rows; with details make, model, trim, mileage, transmission, fuel, colours, owners, title status, dealer facts |
| Rentals | the property rentals category | rental rows; with details beds and baths, amenities, street, walk and transit scores |
| Wide areas | a search area radius up to 500 km: each place searched at the points of a lattice of overlapping circles (200 km: 19 points) | every listing once, wherever two circles overlap |
| Many cities | a country preset (every city Facebook's own Marketplace directory lists, 117 in the US), or search rows each with its own place and filters | up to 300 searches a run |
| Links | search, category and item links from your browser | the link read with its own filters, yours replace them where set |
| Monitor | a memory name on a schedule | only new listings; optionally changed ones again (`UPDATED`, previous price) and the unchanged ones free |
| Sold tracker | listing ids or item links in `watchListings` | one row per listing and run: available, sold, pending, removed, back to available, price change with the old price, the new price and the difference |
| Alerts | a webhook, Discord, Slack, Telegram or WhatsApp | the run's new (or changed) listings sent after the run, free |

### What you get

One `listing` row per Marketplace listing, in Facebook's order. Fill rates measured on 2,009 search rows and 124 detail reads of this Actor's own runs on Apify (2026-10-12):

| Field | What it holds | Filled |
|---|---|---|
| `listingId`, `url` | Facebook's listing id (a string) and its link | 100% |
| `title` | the listing's title | 100% |
| `price`, `priceFormatted` | the price as a number (0 for free items) and as Facebook writes it (`$120`, `€40`) | 100% |
| `city`, `state`, `locationName` | where the listing is (an item link's row takes them from the details' place text) | 100% |
| `createdAt`, `createdAtEpoch` | when the listing was created (ISO 8601 UTC, and as Unix seconds) | 100% of search rows |
| `primaryPhotoUrl` | the main photo | 100% |
| `deliveryTypes` | `IN_PERSON`, `DOOR_PICKUP`, `DOOR_DROPOFF`, `PUBLIC_MEETUP`, `SHIPPING_ONSITE`, `SHIPPING_OFFSITE` | 100% |
| `categoryId` | Facebook's category id | 100% |
| `sellerName`, `sellerId` | the seller as Facebook shows it to logged out visitors | 91% |
| `strikethroughPrice` | the earlier price of a reduced listing | where reduced |
| `subtitles` | what Facebook prints under the title (mileage of a vehicle, the place of a rental) | vehicles and rentals |
| `hasVideo`, `videoId` | whether the listing has a video, and its id | where it has one |
| `isSold`, `isPending`, `isLive` | Facebook's status flags | 100% |
| `queryInTitle` | true when every search word is in the title | every searched row |
| `searchQuery`, `searchLocation`, `searchUrl`, `position` | which search found the row and its place in the results | 100% |
| `changeType`, `previousPrice`, `isRepost`, `repostOfId` | with a memory: `NEW`, `UPDATED` or `UNCHANGED`, the price before a change, and a listing posted again by the same seller with the same title under a new id | monitor runs |

With **Add each listing's details** (or an item link), the row also carries:

| Field | What it holds | Filled |
|---|---|---|
| `description` | the seller's description (as text, HTML or Markdown, cut to a length if you ask) | 98% |
| `photos`, `photoCount` | every photo of the listing, in order | 100% |
| `condition`, `attributes` | the condition and the other attributes Facebook shows | 97%, 92% |
| `currency` | the price's currency code | 100% |
| `categoryName`, `categorySlug` | the category | 95% |
| `latitude`, `longitude`, `distanceKm`, `withinDistance` | where the listing is, how far from the place you searched, and whether that is within your maximum distance | 100% |
| `sellerJoinedAt`, `sellerRating`, `sellerRatingCount` | when the seller joined Marketplace and their rating | 95% |
| `sellerPhotoUrl`, `sellerVerified` | the seller's profile picture and whether Facebook shows its verified badge | where Facebook shows them |
| `vehicle` | make, model, trim, mileage and unit, transmission, fuel type, colours, number of owners, paid off, seller type, MPG, engine size, title status, features, the dealer's website | vehicles (6 of 6 in these runs had make, model, mileage) |
| `dealershipName`, `sellerPhone`, `listingAddress`, `inventoryCount`, `listingInventoryType` | the dealer, its phone and address, and its stock | where Facebook shows them (no dealer listing was shown to logged out visitors in our tests) |
| `videos` | each video of the listing: `url`, `hdUrl`, `thumbnailUrl` (with every photo on) | listings with a video |
| `property` | the detail lines (beds and baths, amenities), street, walk, bike and transit scores, nearby transit | listings of Facebook's Rentals category (the rentals page mixes general items in: 2 to 4 of 8 rows carried property facts in New York runs) |
| `postalCode`, `countryCode` | where Facebook shows them | vehicles mostly |
| `contactEmails`, `contactPhones` | e-mail addresses and phone numbers written in the description (opt in) | where written |

The **sold tracker** writes one `watch` row per watched listing: `status` (`available`, `sold`, `pending`, `removed`), `previousStatus`, `change` and `changes` (`first_check`, `sold`, `pending`, `removed`, `available_again`, `price_changed` or `unchanged`), `price`, `previousPrice`, `priceChange`, `priceChangePercent`, `currency`, `title`, `sellerName`, `locationText`, `createdAt`, `firstSeenAt`, `lastSeenAt` and `checkedAt`.

Every run also writes free rows: a `coverage` row per search, link or watched listing (the place and coordinates used, the exit country, pages read, listings read, delivered, charged, known to the memory, repeated, filtered out, the stop reason and `complete`: whether the search was read as far as you asked, with `newListingsMayLieBeyond` saying whether Facebook had more), `status` rows for anything that could not be read, and a `summary` row with every counter.

#### Example row

```json
{
  "type": "listing",
  "listingId": "1079632694842846",
  "url": "https://www.facebook.com/marketplace/item/1079632694842846/",
  "title": "Bicycle",
  "price": 35,
  "priceFormatted": "$35",
  "city": "Edison",
  "state": "NJ",
  "locationName": "Edison, New Jersey",
  "createdAt": "2026-10-12T03:31:06.000Z",
  "deliveryTypes": ["IN_PERSON"],
  "categoryId": "1658310421102081",
  "isSold": false,
  "sellerName": "Seller name as shown",
  "queryInTitle": true,
  "searchQuery": "bicycle",
  "searchLocation": "New York, New York",
  "position": 1
}
```

A sold tracker row:

```json
{
  "type": "watch",
  "listingId": "1841873350160032",
  "url": "https://www.facebook.com/marketplace/item/1841873350160032/",
  "title": "26 inch bike",
  "status": "available",
  "previousStatus": "available",
  "change": "price_changed",
  "price": 250,
  "previousPrice": 300,
  "priceChange": -50,
  "priceChangePercent": -16.67,
  "currency": "USD",
  "firstSeenAt": "2026-10-12T15:02:11.000Z",
  "lastSeenAt": "2026-10-12T15:02:11.000Z",
  "checkedAt": "2026-10-13T15:00:40.000Z"
}
```

### How much does it cost to scrape Facebook Marketplace?

You pay per event, with Apify's store discounts:

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Listing (per 1,000 listings) | $1.00 | $0.90 | $0.60 | $0.38 |
| Listing detail (per 1,000 listings read with details) | $2.00 | $1.80 | $1.60 | $1.40 |
| Results page (per 1,000 result pages of 24 to 29 listings) | $1.20 | $1.10 | $1.05 | $1.00 |
| Listing check (per 1,000 watched listings checked) | $1.50 | $1.30 | $1.10 | $0.90 |
| Actor start (per run) | $0.006 | $0.006 | $0.006 | $0.006 |

Platinum and Diamond plans pay the Gold price. The Actor start has no store discount.

The Store pricing card shows these same prices per 1,000 events: "$1.00 / 1,000" on the Listing row means one listing costs a tenth of a cent on the Free plan.

- **Listing:** each listing row delivered for the first time in the run, and for the first time under a memory name. A listing that two searches find is delivered and charged once. With **Report changed listings**, a known listing whose price or sold or pending state changed is delivered again and charged as a Listing.
- **Listing detail:** only when you ask for details (or paste an item link), on top of that listing's charge. A listing whose details could not be read is delivered without them and not charged for them.
- **Results page:** each page of search results Facebook answered with listings (24 to 29 a page), new, repeated in the run or already known to the memory. Facebook answers a full page whether you keep 1 listing of it or 24, so this small event pays for the reading itself: a search capped at a few listings, or a monitor that finds little new, still pays its way. On a large search it adds about 4 percent on the Free plan (about 10 percent on Gold) to the listings' price. Empty pages and searches with no result are free.
- **Listing check:** each watched listing Facebook answered in the run (available, sold or pending), or that it no longer shows after the memory saw it (removed). One check per listing per run, whatever changed.
- **Free:** status rows, coverage rows, the summary, unchanged listings you ask to include, rows the filters drop, notifications, a place Facebook does not know, a link it does not read, a listing it does not show (a watched id the memory never saw that answers no listing), a search with no result, a listing repeated in the run or known to the memory, failed calls.

Examples on the Free plan: 1,000 listings from one search cost about $1.05 (the start, 1,000 listings and 36 result pages in our run); 100 listings with details cost about $0.31 (the start, 100 listings, 100 details and 4 result pages); a monitor of 10 searches that reads one page each and finds 5 new listings costs $0.023 (the start, 10 pages and 5 listings); a daily sold tracker of 100 listings costs $0.156 a run (the start and 100 checks; $0.096 on Gold).

### Input

| Field | What it does |
|---|---|
| `searchQueries` | Words to search, one search per line. Every word is searched in every place (and every category). |
| `locations` | Places as Facebook names them (`Austin, Texas`), coordinates (`30.2672,-97.7431`), US ZIP codes (`10001`), postal codes with their country (`10115, Germany`, `M5V, Canada`, `SW1A, United Kingdom`), Marketplace city slugs (`nyc`, `sanfrancisco`) or location ids (`108424279189115`). |
| `searchRadiusKm` | A search area radius up to 500 km around each place: above 65 km each place is searched at several points (100 km: 7, 200 km: 19, 300 km: 61, 500 km: 127). |
| `categories` | Any of `vehicles`, `propertyrentals`, `electronics`, `home`, `pets`, `garden`, `apparel`, `entertainment`, `family`, `hobbies`, `classifieds`, `instruments`, `sports`, `toys`, `free`, `home-improvements`, `office-supplies`. Without words, the category is browsed. |
| `searches` | Search rows, each with its own place and filters: `[{ "query": "sofa", "location": "Austin, Texas", "maxPrice": 300 }, { "location": "nyc", "category": "garden" }]`. |
| `countryPreset` | A two letter country code: every city of Facebook's Marketplace directory for that country (up to 300 searches a run). |
| `startUrls` | Marketplace links: a search (`facebook.com/marketplace/nyc/search?query=bike&maxPrice=300`), a category (`facebook.com/marketplace/nyc/vehicles`) or an item (`facebook.com/marketplace/item/123...`). |
| `watchListings` | Sold tracker: listing ids or item links to check on every run (up to 1,000). |
| `sortBy` | `newest` (when left out), `best_match`, `price_low`, `price_high`, `distance`; when browsing without words also `oldest`, `distance_far`, `mileage_low`, `mileage_high`, `year_new`, `year_old`. |
| `minPrice`, `maxPrice` | A price range in the currency of the place searched. |
| `daysSinceListed` | Listed in the last 1 to 365 days. |
| `listedAfter` | Listed after a date (`2026-10-01`), a moment, or a span back from the run's start (`24 hours`, `7 days`). |
| `deliveryMethod` | `all`, `local_pickup` or `shipping`. |
| `itemCondition` | Any of `new`, `used_like_new`, `used_good`, `used_fair`. |
| `availability` | `available` (when left out) or `sold`: only listings already sold or pending, for price research. |
| `exactMatch` | Facebook's own stricter word match. |
| `vehicleMake`, `transmission`, `vehicleSellerType` | A vehicle make (65 makes), `automatic` or `manual` (`all` clears a link's own), and `private` or `dealer` sellers. |
| `minYear`, `maxYear`, `minMileage`, `maxMileage` | Vehicle filters (a search that sets them returns vehicles). |
| `titleMatchOnly`, `includeKeywords`, `excludeKeywords` | Keep only titles with every search word, or with one of your words; drop titles with others (`broken`, `for parts`). Free. |
| `sameCityOnly` | Drop, free, listings Facebook places in another city than the one searched. |
| `maxDistanceKm`, `distanceFilter` | With details: mark (`withinDistance`) or drop, free, listings farther than this from the place searched. |
| `maxListingsPerSearch` | How many listings each search reads (50 when left out, up to 1,000). |
| `maxListings` | A cap of listing rows for the whole run. |
| `includeDetails`, `includePhotos` | Read each delivered listing's details too (the Listing detail event), with or without every photo. |
| `extractContacts` | With details: e-mail addresses and phone numbers written in the description. |
| `memoryName`, `resetMemory` | Only new listings across runs, and a way to start over. |
| `reportChanges`, `emitUnchanged`, `stopAtKnownPage` | With a memory: deliver changed listings again, include the unchanged ones free, stop a search at the first page the memory knows in full. |
| `descriptionMaxLength`, `descriptionFormat`, `compactOutput`, `excludeEmptyFields`, `flattenOutput` | Output shaping: a cut description, HTML or Markdown, only the core fields, no empty fields, flat rows (`vehicleMake`, `propertyStreet`) for spreadsheets. |
| `webhookUrl`, `webhookHeaders`, `discordWebhookUrl`, `slackWebhookUrl`, `telegramBotToken`, `telegramChatId`, `whatsappAccessToken`, `whatsappPhoneNumberId`, `whatsappTo`, `notificationLimit`, `notifyOnlyChanges` | Alerts after the run (free). Addresses and tokens are kept secret: they never appear in a row or a log line. |
| `exitCountry` | Search every place from an Apify residential exit in this country. |

**Places.** A place name is looked up with Facebook's own location search and used only when one result is written exactly as you wrote it. `Austin` alone matches Austin in Illinois, Georgia, Texas and Minnesota, so it gets a free status row with Facebook's suggestions (their full names, or for two places of one name each one's coordinates, which this field accepts), and you write `Austin, Texas`. A postal code is looked up the same way, with Facebook's own country filter, and the place taken is the one that carries the code: five digits alone are a US ZIP code, any other country's code is written with its country (`10115, Germany`, `2000, Australia`; for the United Kingdom and Canada the first part, `SW1A, United Kingdom`, `M5V, Canada`, as a full code is asked again by its first part). A city slug (the part after `facebook.com/marketplace/`) or a numeric location id goes through Facebook's own route. A place whose name ends in one of the countries below (`Berlin, Germany`), or in a state or province of the United States, Canada, Australia, India, Mexico or Brazil (`Mumbai, Maharashtra`), or in England, Scotland, Wales or Northern Ireland, is searched from an Apify residential exit in that country, as a local visitor sees it; any other place (`Doha, Qatar`), coordinates, slugs and links are searched from the United States unless you set `exitCountry`. Each coverage row names the exit country. The countries: United States, United Kingdom, Canada, Australia, New Zealand, Ireland, Germany, France, Spain, Italy, Portugal, Netherlands, Belgium, Switzerland, Austria, Sweden, Norway, Denmark, Finland, Poland, Czechia (or Czech Republic), Hungary, Romania, Greece, Mexico, Brazil, Argentina, Chile, Colombia, Peru, India, Philippines, Malaysia, Singapore, Indonesia, Thailand, Vietnam, South Africa, Nigeria, Kenya, Israel, United Arab Emirates, Saudi Arabia, Japan, South Korea, Taiwan, Hong Kong, Turkey, Ukraine, Pakistan, Bangladesh, Egypt, Morocco.

**Links.** A pasted search or category link is read the way Facebook's own site reads it: its place, its words, its category and its filters (price, order, days listed, delivery, condition, vehicle year and mileage, availability). A filter you set in the input replaces the link's own value; a filter you leave out keeps it. A link whose place Facebook does not recognise gets a free status row (Facebook would quietly show listings near the visitor instead). An item link is read in full: one row with its details, charged as a Listing plus a Listing detail.

**Browse orders.** Oldest first, farthest first, mileage and model year orders are Facebook's browse orders: they work when browsing a place or a category without words (and without filters Facebook's browse ignores: words, make, transmission, condition, delivery, availability, exact match), and a run that combines them gets a clear status row instead of quietly wrong rows.

**What Facebook adds to a search.** Newest first, Facebook lists the newest listings that carry your words and, once they run out (after 100 to 250 rows in our New York and Chicago runs, about a day of listings), fills up with unrelated new listings. A search with words therefore stops by itself after two result pages in a row in which fewer than one title in ten carries the words (stop reason `no_more_title_matches`); those two pages are delivered and charged like any other (about 50 rows, mostly unrelated, and their 2 result pages), nothing after them. Best match stays on topic much deeper (at most 6 percent unrelated titles down to 600 rows in our runs): choose it for large searches. Related listings appear in either order (a bike for `bicycle`), and a word Facebook does not know can return unrelated listings. Every row says `queryInTitle`; turn on `titleMatchOnly` or the keyword filters to keep only the titles you want (the others are free and do not count toward the number per search). Facebook also shows different samples of a large search to different visitors, so two runs of one search do not return exactly the same rows.

### Monitoring new and changed listings

Set a **Memory name** and schedule the run (every hour, every 15 minutes). Each run reads each search's newest listings (`maxListingsPerSearch`, newest first unless you choose another order), delivers and charges only listings the memory has not seen, and pays a Results page event for each page it reads. With **Report changed listings**, a listing whose price or sold or pending state changed since it was delivered comes again as `UPDATED` with its previous price; with **Include unchanged listings** the rest of what the search found comes too, free, as `UNCHANGED`; **Stop at the first page of known listings** ends a newest first search once a whole page is known. A listing posted again by the same seller with the same title under a new id is flagged `isRepost`. The coverage row says `newListingsMayLieBeyond` when the last page read still held new listings: raise the number per search, or run more often. Pasted search links work in a monitor too: the memory keeps each link's place, so scheduled runs go straight to the link's newest listings. A memory belongs to this Actor; two runs on one memory name at the same time: the second runs free and remembers nothing.

### Sold tracker

Paste listing ids or item links into **Sold tracker** and schedule the run (daily is typical). Each run reads each listing's details (without photos) and compares them with the state the memory kept from the last run (the memory "watchlist" when you set no memory name): `available`, `sold`, `pending` or `removed` (Facebook no longer shows it), whether that changed (`sold`, `pending`, `removed`, `available_again`) and whether the price changed (`previousPrice`, `priceChange`, `priceChangePercent`). The first check of a listing says `first_check`. Facebook sometimes answers "no listing" for a live listing for a moment, so a listing is reported removed only after a second ask from another exit also gets no listing. An id the memory never saw that answers no listing is a free status row, and so is an entry that is not a listing id or link. Set **Notify only new and changed** with an alert to hear only about the listings that changed.

### Alerts

After the run, the run's listings (or, for the sold tracker, its checks) are sent to the targets you set: a JSON POST to your webhook (Make, Zapier, n8n or your own server, with your own headers), a Discord or Slack channel webhook, a Telegram chat through your bot, or WhatsApp through your WhatsApp Cloud API number. Each message names up to `notificationLimit` listings (10 when left out, at most 50) and always says the total; the full rows stay in the dataset. Alerts are free; a target that refuses the message gets a free status row with its HTTP status only.

### Details

Details are read from each listing's own page data, after the listing was found and only for listings that will be delivered. They add the description, every photo, condition and attributes, coordinates, the seller's join date and rating, vehicle facts, dealer facts and property facts. When Facebook stops accepting the reading session this build carries, a run delivers its listings without details (charged as a Listing only), checks no watched listing, and says so in a free status row.

### What this Actor does NOT do

- It never logs in and takes no cookies: no seller profile page, phone number or message history, and the seller's name only where Facebook shows it to logged out visitors (91 percent of rows in our runs). Seller names, ids, descriptions and contacts written in them can be personal data: use them under the rules that apply to you.
- No radius sent to Facebook: it does not honour one for logged out visitors (2, 65 and 500 km gave the same rows for three searches in our tests) and shows about 65 km around one point. A **Search area radius** above 65 km is covered with several points instead; `maxDistanceKm` with details marks or drops listings by their real distance.
- No property for sale category: Facebook answers it with an error page for logged out visitors (New York, Los Angeles, Chicago, London and Toronto in our tests). Property rentals are covered.
- No comparable price, price range or parent listing fields: Facebook leaves them empty for logged out visitors (216 of 216 rows in our tests). Videos come as an id (`videoId`), not a playable file.
- No e-mail alerts and no export into Notion or other connected apps from inside the run: use the webhook with Make, Zapier or n8n, or Apify's own integrations on the dataset.
- Dealer listings: Facebook showed logged out visitors no dealer listing in our tests (Houston, Dallas), so `dealer` sellers can answer nothing and the dealer fields stay empty there.
- No proxy, concurrency or delay settings: the Actor reads through Apify residential exits at a pace Facebook accepts and chooses fresh exits by itself.
- Model is a search word (`honda civic`); make, transmission, year and mileage are filters.
- Search filters do not apply to item links or watched listings: they are read as named.
- It does not promise every listing of a large area: Facebook shows each visitor a sample of the search.
- If Apify restarts a run (a server move, rare), the run goes on where it was and delivers the rest free, as far as its unpaid work allowance reaches (a run with details may stop early then, with a free status row); a search counts what it read before the last save of its progress, so it may read a few listings more or fewer than its number. Run the rest again if you need it.

### FAQ

#### Is there a Facebook Marketplace API?

Facebook offers no public API for reading Marketplace listings. This Actor reads the public, logged out Marketplace the way Facebook's own web app does, and returns the listings as rows you can download or read through Apify's API.

#### Can I scrape Facebook Marketplace without logging in?

Yes. This Actor needs no Facebook account, no cookies and no password; it reads what Facebook shows to logged out visitors, through Apify residential proxies.

#### How do I get alerts for new Facebook Marketplace listings?

Set a memory name and schedule the run: each run delivers only listings it has not delivered before under that name. Add a Discord, Slack, Telegram or WhatsApp target, or a webhook, and each run sends its new listings there.

#### How do I know when a Facebook Marketplace listing is sold?

Put the listing ids or links into the sold tracker and schedule the run. Each run says per listing whether it is available, sold, pending or removed, and whether that or its price changed since the last run.

#### How do I scrape Facebook Marketplace by location?

Write each place as Facebook names it (`Austin, Texas`), as coordinates, as a US ZIP code or as a Marketplace city slug, or paste a Marketplace search link from your browser, or set a country preset for every Marketplace city of a country. A place named with one of 53 countries the Actor knows (the list is under Input), or with a state of the United States, Canada, Australia, India, Mexico or Brazil, is searched from an exit in that country; others from the United States; the coverage row names the exit.

#### Why does this Actor run with limited permissions?

It follows least privilege: it reads and writes only its own storages. The memory for new listings and the sold tracker is a named store the Actor creates itself under your account.

### API

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~facebook-marketplace-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"searchQueries": ["bicycle"], "locations": ["New York, New York"], "maxListingsPerSearch": 20}'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_TOKEN>' });
const run = await client.actor('automation_craft/facebook-marketplace-scraper').call({ searchQueries: ['sofa'], locations: ['Chicago, Illinois'], maxPrice: 300 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("automation_craft/facebook-marketplace-scraper").call(run_input={"watchListings": ["1841873350160032"], "memoryName": "my watchlist"})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

### Changelog

See the Changelog tab. 0.2 (2026-10-12): sold tracker, categories, browsing, country presets, ZIP codes and city slugs, sold only, make and transmission, more orders and filters, change reporting, alerts. 0.1 (2026-10-12): first release.

### More data tools by Automation Craft

- [Facebook Ads Library Scraper - Ad Library & Spy](https://apify.com/automation_craft/facebook-ads-library-scraper)
- [Meta Ads Library Scraper - Ad Library, All Filters](https://apify.com/automation_craft/meta-ads-library-scraper)
- [Meta Ad Spend Scraper: Advertiser Spend by Country](https://apify.com/automation_craft/meta-ad-spend-scraper)
- [Threads Scraper: Posts, Search, Tags, Replies](https://apify.com/automation_craft/threads-scraper)
- [Airbnb Scraper: Prices, Calendar, Reviews](https://apify.com/automation_craft/airbnb-scraper)
- [Amazon Product Scraper - Search, Best Sellers](https://apify.com/automation_craft/amazon-data-scraper)
- [Google Trends Scraper - Compare and Trending Now](https://apify.com/automation_craft/google-trends-scraper)

# Changelog

This Actor's version history is a separate document: https://apify.com/automation_craft/facebook-marketplace-scraper/changelog.md

# Actor input Schema

## `searchQueries` (type: `array`):

What to search for, one search per line ("iphone 15", "sofa", "honda civic"). Every word is searched in every place below. Up to 300 searches per run (words times places times categories).

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

Where to search, one place per line: as Facebook names it ("Austin, Texas", "London, United Kingdom", "Toronto, Ontario"), as coordinates ("30.2672,-97.7431"), as a Marketplace city slug ("nyc", "sanfrancisco", from facebook.com/marketplace/sanfrancisco) or location id ("108424279189115"), as a US ZIP code ("10001") or as a postal code with its country ("10115, Germany", "M5V, Canada", "SW1A, United Kingdom", "2000, Australia"). A place Facebook writes another way, or two places of the same name, get a free row with Facebook's own suggestions and are not searched. A place whose name ends in one of 53 countries the Actor knows (the README lists them), or in a state or province of the United States, Canada, Australia, India, Mexico or Brazil, is searched from an exit in that country, as a local visitor sees it; other places and coordinates from the US (each coverage row names the exit). A place without search words is browsed: the newest listings near it.

## `searchRadiusKm` (type: `integer`):

Facebook shows a visitor who is not logged in the listings of about 65 km around one point and ignores a wider radius. Above 65 km, each place is searched at several points of a lattice covering this radius (100 km: 7 points, 200 km: 19, 300 km: 61, 500 km: 127), each its own search with its own coverage row; a listing found from two points is delivered and charged once. Up to 300 searches a run. Leave it out for one point per place.

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

Search (or browse) inside these Marketplace categories; each category is its own search in every place. Free stuff searches listings priced 0. Property for sale is not shown to visitors who are not logged in and is not offered.

## `searches` (type: `array`):

One search per row, each with its own place and filters: \[{ "query": "sofa", "location": "Austin, Texas", "maxPrice": 300 }, { "location": "nyc", "category": "garden" }]. Keys: query (none browses), location (required), category, minPrice, maxPrice, daysSinceListed. Rows run in addition to the words times places above.

## `countryPreset` (type: `string`):

A two letter country code (US, CA, GB, AU, IN, MX, BR, NZ and others): every city Facebook's own Marketplace directory lists for the country is searched (or browsed, without words), up to 300 searches a run (the US directory lists 117 cities). Each city is searched from an exit in that country.

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

Facebook Marketplace links to read, one per line: a search link (facebook.com/marketplace/nyc/search?query=bike\&maxPrice=300), a category link (facebook.com/marketplace/nyc/electronics) or an item link (facebook.com/marketplace/item/123...). A search link keeps its own place and filters; a filter you set below replaces the link's. An item link is read in full (Listing plus Listing detail). A category link (vehicles and rentals included) reads the sample Facebook shows on that page for that place.

## `watchListings` (type: `array`):

Listing ids or item links (facebook.com/marketplace/item/123...) to check on every run: each gets one row saying whether it is available, sold, pending or removed, and whether that or its price changed since the last run (old price, new price, change), against the last seen state the Actor keeps in its memory. One Listing check event per listing checked; an id Facebook never showed is free. Without a memory name a watch only run keeps its state under the name "watchlist"; with searches or links too, set a memory name.

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

The order Facebook lists the results in. Newest first when left out (each listing's date is on every row either way). Newest first, Facebook fills up with unrelated new listings once the listings with your words run out (after 100 to 250 rows in our runs); a search stops there by itself. Best match stays on topic much deeper: choose it for large searches. The orders marked browsing only are served by Facebook only for places or categories browsed without words, and with them Facebook applies a price range only.

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

Only listings at or above this price, a whole number in the currency of the place searched.

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

Only listings at or below this price, a whole number in the currency of the place searched.

## `daysSinceListed` (type: `integer`):

Only listings Facebook says were listed in the last N days (1 to 365).

## `listedAfter` (type: `string`):

Only listings created after this moment: a date (2026-10-01), a date and time (2026-10-01T08:00:00Z) or a span back from the run's start ("24 hours", "7 days", "2 weeks"). Older listings are dropped free; newest first, the search stops at the first page that is entirely older.

## `deliveryMethod` (type: `string`):

Local pickup, shipping, or both (all, when left out).

## `itemCondition` (type: `array`):

Only listings in these conditions (Facebook's own filter; search rows carry no condition field, the details carry it).

## `availability` (type: `string`):

Available listings (when left out), or only listings already sold or pending (Facebook's out of stock filter), for price research. Searches with words or categories.

## `exactMatch` (type: `boolean`):

Ask Facebook to match the search words more strictly (its own exact filter). In our tests it changed little; titleMatchOnly below keeps only titles with every word.

## `vehicleMake` (type: `string`):

Vehicles of this make only (Facebook's own make filter, each make checked live). Without search words the make's name is searched.

## `transmission` (type: `string`):

Vehicles with this transmission only (Facebook's own filter).

## `vehicleSellerType` (type: `string`):

Vehicles from private sellers or from dealers only (Facebook's own filter). In our tests Facebook showed visitors who are not logged in no dealer listing (Houston, Dallas): dealers only can answer nothing.

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

Vehicles only: the earliest model year (Facebook's vehicle filter; listings that are not vehicles drop out of a search that sets it).

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

Vehicles only: the latest model year.

## `minMileage` (type: `integer`):

Vehicles only: the lowest odometer reading, in the units the listing uses (miles in the US).

## `maxMileage` (type: `integer`):

Vehicles only: the highest odometer reading.

## `titleMatchOnly` (type: `boolean`):

Facebook adds related listings to a search (a bike for bicycle; a word it does not know can return unrelated listings). On: a listing whose title lacks one of the search words is dropped free and does not count toward "Listings per search"; a search stops after three pages in a row without such a listing. Off when left out: every row says whether its title has every word (queryInTitle). Search links read from the link's own words.

## `includeKeywords` (type: `array`):

Keep only listings whose title carries one of these words or phrases (every word of a phrase, any order). Others are dropped free and do not count toward the number per search.

## `excludeKeywords` (type: `array`):

Drop, free, every listing whose title carries one of these words or phrases ("broken", "for parts").

## `sameCityOnly` (type: `boolean`):

Drop, free, listings Facebook places in another city than the place searched (Facebook adds nearby towns). For places Facebook names, ZIP codes and the cities of a country preset; not for coordinates, links or a bare city slug.

## `maxDistanceKm` (type: `integer`):

With details only (a listing's coordinates come with them): every row says withinDistance; with the distance filter on drop, rows farther than this are dropped free. Facebook ignores a search radius for visitors who are not logged in.

## `distanceFilter` (type: `string`):

Mark (when left out) or drop the rows beyond the maximum distance.

## `maxListingsPerSearch` (type: `integer`):

How many listings each search (or search link) reads, in Facebook's order; 50 when left out. A listing already delivered in this run, or known to the memory, counts as read and is free.

## `maxListings` (type: `integer`):

The cap of listing rows for the whole run, across every search. No cap when left out: the per search number and your charge limit bound the run.

## `includeDetails` (type: `boolean`):

Read each delivered listing's own page too: description, every photo, condition and attributes, coordinates, the seller's join date and rating, vehicle facts and property facts (the Listing detail event, on top of the Listing). Off when left out.

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

Read every photo of a listing with its details (on when left out). Off saves a call per listing; the main photo stays on every row.

## `extractContacts` (type: `boolean`):

With details: list the e-mail addresses and phone numbers written in the description (contactEmails, contactPhones). Off when left out. These are personal data: use them under the rules that apply to you.

## `memoryName` (type: `string`):

A short name (letters, digits, spaces, . \_ , : # ( ) -). Listings delivered under this name are never delivered or charged again in later runs with the same name: schedule the run and get only new listings. The memory belongs to this Actor; two runs on one name at the same time: the second runs free.

## `resetMemory` (type: `boolean`):

Forget every listing the memory holds before this run (it needs a memory name). Only a fresh run resets; a run the platform restarts never resets again.

## `reportChanges` (type: `boolean`):

With a memory name: a listing delivered before whose price, or sold or pending state, changed is delivered again (changeType UPDATED, with the previous price and state) and charged as a Listing. New rows say changeType NEW.

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

With a memory name: also deliver the listings the memory knows and that did not change (changeType UNCHANGED), free and without details.

## `stopAtKnownPage` (type: `boolean`):

With a memory name: a search stops at the first page whose listings the memory all knows (newest first, the rest is older).

## `descriptionMaxLength` (type: `integer`):

Cut descriptions to this many characters (0 or left out: the whole description; a cut row says descriptionTruncated).

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

The description as Facebook's plain text (when left out), or as HTML (descriptionHtml) or Markdown (descriptionMarkdown), or all.

## `compactOutput` (type: `boolean`):

Only the core fields per row (title, price, place, date, photo, seller, description, link, the search), for AI agents and small exports.

## `excludeEmptyFields` (type: `boolean`):

Drop fields that are empty (null, empty text, empty lists) from each row.

## `flattenOutput` (type: `boolean`):

Turn the vehicle and property objects into top level fields (vehicleMake, vehicleMileage, propertyStreet), for spreadsheets and CSV.

## `webhookUrl` (type: `string`):

After the run, a JSON POST with the run's listings (up to the notification limit) to this https address (Make, Zapier, n8n or your own server).

## `webhookHeaders` (type: `object`):

Extra headers for the webhook ({"Authorization": "Bearer ..."}); their values are masked in the run's rows and logs.

## `discordWebhookUrl` (type: `string`):

A Discord channel webhook (https://discord.com/api/webhooks/...): the run's listings as messages.

## `slackWebhookUrl` (type: `string`):

A Slack incoming webhook (https://hooks.slack.com/services/...).

## `telegramBotToken` (type: `string`):

A Telegram bot token from @BotFather; goes with the chat id.

## `telegramChatId` (type: `string`):

The chat or channel to post in (a number, or @channelname).

## `whatsappAccessToken` (type: `string`):

A WhatsApp Cloud API access token (Meta for Developers); goes with the phone number id and the recipient.

## `whatsappPhoneNumberId` (type: `string`):

Your WhatsApp Business phone number id.

## `whatsappTo` (type: `string`):

The recipient's number in international form, digits only (14155552671); WhatsApp delivers only if the recipient wrote to your number in the last 24 hours.

## `notificationLimit` (type: `integer`):

How many listings a notification names (10 when left out); the total is always said.

## `notifyOnlyChanges` (type: `boolean`):

With a memory name or a sold tracker: name only new and changed listings (and checks that changed).

## `exitCountry` (type: `string`):

A two letter country code: search every place from an Apify residential exit in this country instead of the country the place name says.

## Actor input object example

```json
{
  "searchQueries": [
    "bicycle"
  ],
  "locations": [
    "New York, New York"
  ],
  "maxListingsPerSearch": 20
}
```

# Actor output Schema

## `items` (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 = {
    "searchQueries": [
        "bicycle"
    ],
    "locations": [
        "New York, New York"
    ],
    "maxListingsPerSearch": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/facebook-marketplace-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 = {
    "searchQueries": ["bicycle"],
    "locations": ["New York, New York"],
    "maxListingsPerSearch": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/facebook-marketplace-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 '{
  "searchQueries": [
    "bicycle"
  ],
  "locations": [
    "New York, New York"
  ],
  "maxListingsPerSearch": 20
}' |
apify call automation_craft/facebook-marketplace-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation_craft/facebook-marketplace-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/e30lXH36MBVZo8dgD/builds/OeGorFTu4Csupl0GE/openapi.json
