# Facebook Marketplace Scraper (`dami_studio/facebook-marketplace-scraper`) Actor

Search Facebook Marketplace by keyword and place and get one row per listing: title, price, currency, city, listing link, photo and posting date. Filter by price, condition, category and recency. No login, no cookies, no browser - and the cheapest Marketplace scraper on the market.

- **URL**: https://apify.com/dami\_studio/facebook-marketplace-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, E-commerce, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 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.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Facebook Marketplace Scraper

Give it a search term and a place, and get back the Facebook Marketplace listings that match: title, price, currency, city, listing link, photo, and when it was posted. Filter by price range, category, item condition, how recently it was listed, and sort the way the site does. No Facebook account, no cookies, no login, no browser.

- Search many terms in one run — each listing is one row, deduplicated across terms so you are never charged twice for the same item.
- Works anywhere Marketplace does: give a city name, a Marketplace city slug, or raw coordinates, and the search is centred there.
- Price range, item condition, listed-in-the-last-N-days, category and sort order are all real filters applied by Facebook, not filtered afterwards.
- Paste a Marketplace search URL straight from your browser and it is parsed into the same filters.
- Runs with empty input return one labelled sample row, free, so you can see the exact output shape before spending anything.

### Price

**$0.40 per 1,000 listings**, plus a **$0.0025 start fee per run** (billed per gigabyte of run memory, so exactly that on the default 1 GB).

This is **the cheapest Facebook Marketplace scraper on the market**, and it is the same rate on every plan, free or paid. There are no volume tiers, no minimum spend, no subscription and no add-on fees. What you read here is what you pay on day one and on day four hundred.

| Listings | Total cost |
|---|---|
| 100 | $0.0425 |
| 1,000 | $0.4025 |
| 10,000 | $4.0025 |
| 100,000 | $40.0025 |

#### What is actually charged

- **One `listing-scraped` event per listing row written to the dataset.** Nothing else is metered per row.
- **Free:** the sample row an empty run returns, and every diagnostic row — a blocked target, a dead URL, a search that matched nothing. Those rows all carry `"charged": false`.
- Listings that already appeared earlier in the same run are dropped before they are charged.
- A search term that matches nothing produces an uncharged diagnostic row, not a bill.
- A run that finds nothing costs the start fee and nothing more.
- Rows never leave the dataset without a charge, and are never charged without a row. The billed event is a named one, so there is no price quietly attached to `apify-default-dataset-item` — the trick that makes some scrapers bill you for their own error messages.

### Input

```json
{
  "searchTerms": [
    "road bike",
    "mountain bike"
  ],
  "location": "Austin, TX",
  "minPrice": 100,
  "maxPrice": 800,
  "daysSinceListed": 7,
  "sortBy": "newest",
  "maxItems": 60
}
```

| Field | What it does |
|---|---|
| `searchTerms` | One or more things to search for. Up to 10 per run. The row budget is shared evenly between them. |
| `location` | Where to search. A plain city name works ("Austin, TX", "Manchester", "Toronto"), so does a Marketplace city slug ("nyc", "la", "sanfrancisco"), and so does a raw "latitude,longitude" pair. Marketplace search is location-based, so this is required unless you set latitude and longitude yourself or paste a URL that carries them. |
| `latitude / longitude` | Exact coordinates to centre the search on, if you would rather not rely on a place name. |
| `startUrls` | Marketplace search URLs copied from your browser. The search term, place, price range, condition, sort order and category in the URL are all read out of it and applied. |
| `minPrice / maxPrice` | Price range, in the currency of the place you are searching. Both optional. |
| `condition` | Any of `new`, `used_like_new`, `used_good`, `used_fair`. One value is the most reliable; several are passed through exactly as the site sends them. |
| `daysSinceListed` | Only listings posted within the last N days, 1 to 30. |
| `category` | A Marketplace category id, if you want to narrow to one department. |
| `sortBy` | One of `best_match`, `newest`, `price_low_to_high`, `price_high_to_low`, `distance`. Defaults to Facebook's own relevance order. |
| `exactMatch` | Set true to make Facebook match the phrase more strictly instead of loosely. |
| `availability` | Set to `sold` to look at sold listings instead of available ones. |
| `radiusKm / radiusMiles` | Passed through to Facebook exactly as its own filter sends it. Read the limitations section before relying on it. |
| `maxItems` | Total rows across all terms. Default 20, hard ceiling 1,000. Keep it low while testing — you pay per row. |
| `proxyUrls` | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as `http://user:pass@host:port`. |

Run it with **empty input** and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.

### Output

One row per listing. A real row from a real run:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "listing",
  "searchTerm": "road bike",
  "listingId": "1510649247532466",
  "listingUrl": "https://www.facebook.com/marketplace/item/1510649247532466/",
  "title": "RoyalBaby Freestyle 20 Inch Kids Bicycle with Kickstand and Water Bottle, White",
  "price": 68,
  "priceFormatted": "$68",
  "currency": "USD",
  "originalPrice": null,
  "priceMin": null,
  "priceMax": null,
  "city": "New York",
  "state": "NY",
  "locationName": "New York, New York",
  "imageUrl": "https://scontent.xx.fbcdn.net/v/t39.84726-6/767388547_2298847434264202_example_n.jpg",
  "videoUrl": null,
  "postedAt": "2026-08-07T14:44:36.000Z",
  "postedAtTimestamp": 1786301076,
  "daysListed": 9,
  "isSold": false,
  "isPending": false,
  "isLive": true,
  "categoryId": "1658310421102081",
  "deliveryTypes": [
    "IN_PERSON",
    "PUBLIC_MEETUP"
  ],
  "subtitle": null,
  "fromGroup": null,
  "scrapedAt": "2026-08-16T09:14:02.771Z"
}
```

#### Field notes

- `listingUrl` — the permanent link to the item. Stable, and safe to use as a primary key across runs.
- `price` — a plain number in the listing currency. A "Free" listing is 0. A listing with no price at all is null.
- `priceFormatted` — exactly the string Facebook shows, symbol included.
- `currency` — the ISO code, read from the symbol Facebook formats the price with. See the limitations section.
- `originalPrice` — the crossed-out price when the seller has cut it; null otherwise. `originalPrice` minus `price` is the discount.
- `priceMin / priceMax` — only populated for listings sold as a price range rather than a single figure.
- `city / state / locationName` — where the seller says the item is. `state` is empty in countries that do not use one.
- `imageUrl` — the primary photo, as a Facebook CDN link. Those links expire after a while, so download what you want to keep.
- `postedAt / postedAtTimestamp` — when the listing was created, as ISO 8601 in UTC and as epoch seconds.
- `daysListed` — how long the item has been sitting there, in days. Useful for spotting stale listings.
- `isSold / isPending / isLive` — the item status at read time.
- `categoryId` — Facebook's internal department id for the item. Stable, so you can group by it, but Facebook does not publish a public name for it.
- `deliveryTypes` — how the seller will hand it over, e.g. `IN_PERSON`, `PUBLIC_MEETUP`, `SHIPPING`.
- `subtitle` — the extra line Facebook shows under vehicle listings — mileage and similar. Null for most categories.
- `fromGroup` — set when the item was cross-posted from a buy-and-sell Group; null otherwise.

Every real row carries `"charged": true`. Sample rows carry `"_sample": true` and diagnostic rows carry `"_diagnostic": true` with an `errorCode` you can filter on, and neither is ever billed.

### How it works

- It calls the same public search endpoint the Marketplace page itself calls, and reads the listings straight out of the JSON it returns — no page rendering, no headless browser, no Facebook account, no cookies and no login.
- Only the search results are fetched, which is about 2.6 KB of traffic per listing. That is deliberate: the equivalent rendered page is seventeen times bigger for the same rows, and paying for those bytes is what makes most scrapers of this site expensive.
- Requests go out through the kind of addresses Facebook serves Marketplace to, and the cost of that egress is already inside the price you see — there is nothing for you to configure and no proxy of your own to buy.
- Each search is paged until your `maxItems` is reached, and listing ids already seen in the run are skipped so you are never charged for the same item twice.
- If Facebook rotates the internal id of its own search query, the Actor re-reads the current one from the page and carries on instead of failing.

### What people use it for

- Reselling and arbitrage — run the same terms every morning, sort by `postedAt`, and see only what appeared since yesterday.
- Price research before buying or listing something: pull 200 comparable items, then take the median of `price` rather than guessing.
- Local market monitoring for a category you trade in — furniture, bikes, tools, phones — with `daysSinceListed` set to 1 so each run is only new stock.
- Spotting price drops: `originalPrice` minus `price` gives you every listing whose seller has already cut the asking price.
- Feeding a dashboard or spreadsheet with a clean typed table instead of scrolling an infinite feed by hand.
- Lead lists for dealers and buyers in one metro area — search a term, centre it on the city, keep everything under a price ceiling.

### Reading the output

Every run writes three kinds of row, and they are easy to tell apart:

- **Real rows** carry `"charged": true` and `"recordType": "listing"`. These are the rows you paid for, one billed event each.
- **The sample row** carries `"_sample": true` and `"charged": false`. There is exactly one, it only appears when the input had no search terms and no URLs, and it exists so you can look at the shape of the output before you spend anything.
- **Diagnostic rows** carry `"_diagnostic": true`, `"charged": false` and an `errorCode` you can switch on: `NO_LOCATION` when the run had no usable place to search, `NO_RESULTS` when a term matched nothing, `BLOCKED` when Facebook refused every address tried for that term, `NETWORK` when it could not be reached at all, `BAD_INPUT` when a supplied URL was not a Marketplace URL, and `TIME_BUDGET` when the run ran out of time before reaching a term.

If you only want the data, filter on `charged == true`. The count of those rows always equals the number of events you were billed for, so the dataset is its own invoice.

### Searching by URL instead of by term

If you have already built the search you want in a browser, paste the URL into `startUrls` and it is taken apart for you. All of these work:

```
https://www.facebook.com/marketplace/nyc/search?query=road%20bike
https://www.facebook.com/marketplace/la/search?query=sofa&minPrice=50&maxPrice=400
https://www.facebook.com/marketplace/austin/search?query=guitar&daysSinceListed=7&sortBy=creation_time_descend
```

The place in the path becomes the search location, and `query`, `minPrice`, `maxPrice`, `itemCondition`, `daysSinceListed`, `sortBy`, `exact` and `category_id` all become the matching filters. Anything you also set in the input fields wins over what the URL said, so you can paste one URL and vary one knob.

### Limitations

- The seller's name is not returned. Facebook does not put it on the public search results at all — the field comes back empty for every logged-out caller, and the only page that carries it costs far more in traffic than a listing is worth. Rather than sell you a column that is always blank, it is left out.
- Item condition is a filter you can search by, but not a field that comes back, for the same reason: it lives on the individual listing page rather than in the search results.
- `radiusKm` is sent to Facebook exactly as its own radius filter sends it, but the public search applies its own catchment around the coordinates: the same search at 2 km and at 500 km returned the same listings in the same order. Treat the coordinates, not the radius, as what decides the search area.
- Facebook returns 24 listings per page and caps it there, so a large `maxItems` means several pages and a longer run. Very deep paging eventually stops returning new items — Marketplace itself does the same.
- `currency` is read from the symbol Facebook formats the price with. Facebook disambiguates the ones that matter (`CA$`, `MX$`, `A$`, `R$`), so this is reliable, but a locale using a symbol shared by two currencies can be ambiguous. `priceFormatted` always holds the literal string if you want to check.
- Photo links point at Facebook's CDN and expire after a period. Download anything you intend to keep.
- `categoryId` is Facebook's internal department id. There is no public name for it, so no name is invented.
- Marketplace throttles hard at peak hours. A term that is refused produces an uncharged `BLOCKED` diagnostic row and the run carries on to your other terms; re-running usually succeeds.
- Only listings visible to a logged-out visitor are returned. Anything Facebook restricts to signed-in users, or to members of a private Group, is out of reach and always will be — this Actor never signs in.
- The hard ceilings are 1,000 rows and 10 search terms (or Marketplace URLs) per run. Split bigger jobs across runs.

### Questions

**Do I need a Facebook account or cookies?**

No, and you should not supply any. The Actor reads only what Facebook serves to the public, with no session of any kind. Nothing you own is ever logged in and nothing is ever at risk of being flagged.

**Why do I have to give a location?**

Because Marketplace is a local marketplace — the search endpoint returns an empty result for a query with no coordinates. A plain city name is enough; it is turned into coordinates for you.

**Why is there no seller name?**

Facebook does not include it in public search results. It is only on the individual listing page, and fetching that page per item would cost many times what a row sells for. Selling you an always-blank column would be worse than saying so.

**What happens if a term matches nothing?**

You get one uncharged diagnostic row for that term with `errorCode: "NO_RESULTS"`, and the run carries on to your other terms. You are never billed for a term that returned nothing.

**Will the run fail if Facebook blocks it?**

No. A refused search produces an uncharged `BLOCKED` diagnostic row and the run still finishes as succeeded. A failed run would still bill you the start fee, which would mean paying to be told something went wrong.

**Can I run this on a schedule?**

Yes. Nothing is held between runs, so the same input is safe to repeat. Use `listingId` to work out which rows are new since last time, and `postedAt` to sort them.

**How do I get exactly the rows I paid for?**

Filter the dataset on `"charged": true`. Sample and diagnostic rows are always `false`, and the number of charged rows always equals the number of billed events.

**Does it work outside the United States?**

Yes. Any place Marketplace operates in works — city names in the UK, Canada, Australia, Europe, Latin America and Asia all resolve, and prices come back in the local currency.

# Actor input Schema

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

What to look for on Marketplace. Up to 10 terms per run; the row budget below is shared evenly between them.

## `location` (type: `string`):

Where to search. A plain city name works ("Austin, TX", "Manchester", "Toronto"), so does a Marketplace city slug ("nyc", "la", "sanfrancisco"), and so does a raw "latitude,longitude" pair. Marketplace search is local, so this is required unless you set the coordinates below or paste a URL that carries them.

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

Total listings to return across all search terms. The budget is shared evenly between the terms, so four terms and 40 rows gives you ten of each. Keep it low while you are testing - you pay per row.

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

Only listings at or above this price, in the currency of the place you are searching. Leave empty for no lower bound.

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

Only listings at or below this price, in the currency of the place you are searching. Leave empty for no upper bound.

## `condition` (type: `array`):

Only listings the seller marked with these conditions. A single value is the most reliable; several are passed through exactly as the site's own filter sends them. Note that the condition is a filter you can search by, not a column that comes back - Facebook does not publish it in search results.

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

Only listings posted in the last N days, from 1 to 30. Leave empty for all listings.

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

How Facebook should order the results. Defaults to its own relevance ranking.

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

Search available listings, or sold ones.

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

Make Facebook match your term as a phrase rather than loosely. Useful for a model number or an exact product name.

## `category` (type: `string`):

A Facebook Marketplace category id, if you want to narrow the search to one department. Leave empty to search everything.

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

Marketplace search URLs copied straight from your browser, for example https://www.facebook.com/marketplace/nyc/search?query=road%20bike\&minPrice=100. Up to 10 per run. The term, place, price range, condition, sort order and category in the URL are all read out of it. Anything you set in the fields above wins over the URL.

## `latitude` (type: `string`):

Centre the search on these exact coordinates instead of a place name, for example 30.2672. Set both latitude and longitude, or neither.

## `longitude` (type: `string`):

Centre the search on these exact coordinates instead of a place name, for example -97.7431. Set both latitude and longitude, or neither.

## `radiusKm` (type: `integer`):

Sent to Facebook exactly as its own radius filter sends it. Be aware that the public search applies its own catchment around the coordinates: the same search at 2 km and at 500 km returns the same listings. The coordinates, not the radius, decide the search area.

## `proxyUrls` (type: `array`):

Leave this empty. The run brings its own egress and its cost is already inside the price you see. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## `sessionCookies` (type: `array`):

Leave this empty unless you need it. Runs are logged out by default and that is enough for public Facebook content. Facebook shows some things only to a signed-in account, and it limits how fast any one account may read; supplying your own cookie uses your account and your own rate limit, shared with nobody. In Chrome: open facebook.com while signed in, press F12, then Application > Cookies > https://www.facebook.com, and paste the values as "c\_user=<value>; xs=<value>". One line per account. Treat these like a password: anyone with them can act as that account, and Facebook may sign the session out or restrict the account for automated use.

## Actor input object example

```json
{
  "searchTerms": [
    "road bike"
  ],
  "location": "Austin, TX",
  "maxItems": 20
}
```

# Actor output Schema

## `results` (type: `string`):

Every row in the default dataset: searchTerm, listingId, listingUrl, title, price, priceFormatted, currency, originalPrice, priceMin, priceMax, city, state, locationName, imageUrl, videoUrl, postedAt, postedAtTimestamp, daysListed, isSold, isPending, isLive, categoryId, deliveryTypes, subtitle, fromGroup. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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 = {
    "searchTerms": [
        "road bike"
    ],
    "location": "Austin, TX",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/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 = {
    "searchTerms": ["road bike"],
    "location": "Austin, TX",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/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 '{
  "searchTerms": [
    "road bike"
  ],
  "location": "Austin, TX",
  "maxItems": 20
}' |
apify call dami_studio/facebook-marketplace-scraper --silent --output-dataset

```

## MCP server setup

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