# Craigslist Scraper - Cars, Housing, Jobs & For Sale (`abotapi/craigslist-classifieds-scraper`) Actor

Scrape Craigslist postings in any of 700+ cities worldwide: price, title, posting text, attributes, photos, coordinates, neighbourhood and posted date. Search by city, section, keyword and price, or paste search and posting links. Incremental monitoring, resume, MCP export.

- **URL**: https://apify.com/abotapi/craigslist-classifieds-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Lead generation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 posting records

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?

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

## Craigslist Scraper - Cars, Housing, Jobs & For Sale

Scrape Craigslist (craigslist.org), the world's largest local classifieds
network, across 700+ city sites in 79 countries. The actor returns
postings with price, title, posting text, the section's own attribute list,
photos, neighbourhood, coordinates and posted date. It also monitors
postings across runs (NEW, UPDATED, REAPPEARED, EXPIRED), resumes
interrupted pulls, and pipes results into your apps through MCP connectors.

### Why This Scraper?

- **A whole search in one go.** A craigslist search returns its entire
  result set, so a 9,000-posting search comes back complete and you are
  never charged twice for the same posting.
- **Coordinates, neighbourhood and posted time on every row**, not just on
  the postings you open in full. You are not charged extra to find out
  where a posting is or when it went up.
- **The section's real attributes.** Cars come back with make and model,
  year, odometer, title status, fuel, transmission, drivetrain, paint and
  body type. Housing comes back with bedrooms, bathrooms, move-in date,
  laundry, parking and pet rules. Jobs come back with compensation and
  employment type.
- **Every city, not just the US.** Name a city as `sfbay`, as `SF bay
  area`, or by pasting any link from it. London, Toronto, Berlin, Sydney,
  Tokyo and 700 more resolve the same way.
- **Works on every Apify plan**, including the free one.
- **Change monitoring:** scheduled runs classify every posting as NEW,
  UPDATED, UNCHANGED, REAPPEARED or EXPIRED, with per-campaign state you
  can name or share.
- **MCP connector export:** send results to Notion and other connectors
  without changing the dataset output.
- **Structured failures:** a blocked or broken run fails loudly with a
  clear message instead of returning an empty dataset. A search the source
  rejects says so in different words from a connection problem, so you can
  tell a typo from an outage.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live posting.

| Field | Example value |
|---|---|
| kind | posting |
| recordId / postingId | 7900000000 |
| postingUuid | AbCdEfGhIjKlMnOpQrStUv |
| url | https://www.craigslist.org/view/d/example-city-bicycle/AbCdEfGhIjKlMnOpQrStUv |
| title | Example road bicycle, lightly used |
| price / priceString / currency | 625 / $625 / USD |
| categoryAbbr / categoryName / section | bik / bicycles - by owner / for sale |
| postedDate / updatedDate / createdDate | 2026-09-04T00:00:00+00:00 |
| cityHost / cityName / areaId | sfbay / SF bay area / 1 |
| subareaAbbr / subareaName | eby / east bay |
| neighborhood / locationDescription | example neighbourhood / example town |
| displayAddress | 100 Example Street |
| latitude / longitude | 37.7749 / -122.4194 |
| imageUrls / imageCount | \["https://images.craigslist.org/00d0d\_example\_1200x900.jpg"] / 4 |
| bedrooms / sqft | 3 / 1200 |
| odometer | 84000 |
| salary / company / jobTitle | Competitive hourly pay / Example Co / Example role |
| monthlyPaymentString | $312/mo |
| eventFirstDate / eventLastDate / openHouses | 2026-09-10 / 2026-09-12 / \["2026-09-07"] |
| detailLoaded | true |
| description | Bought new in 2012. Lightly used, excellent condition. |
| attributes | \[{"key": "condition", "label": "condition", "value": "good", "type": "key\_value"}] |
| attributesByKey | {"condition": "good", "bicycle\_frame\_material": "aluminum"} |
| hasContactInfo | true |
| repostOf / seeMyOther / notices | 7899999999 / 1 / \[] |
| changeType / changedFields | NEW / \[] |
| firstSeenAt / lastSeenAt | 2026-09-04T00:00:00+00:00 |

Result rows carry everything above the `detailLoaded` line and
`detailLoaded: false`. Postings read in full (a pasted posting link, or the
detail toggle) add the posting text, the attribute list, the display
address and the update time, and set `detailLoaded: true`. Section extras
appear on the sections that have them: bedrooms and square footage on
housing, odometer on vehicles, salary, company and job title on jobs, event
dates and open houses where the section has them.

**Contact data.** The actor reports `hasContactInfo` so you know whether a
poster left a way to be reached, and returns the posting text as written.
It does not open the source's reply relay, so it never collects a personal
email address or phone number from a contact route.

### How to Use

#### City search (default)

1. Set **Cities** to one or more craigslist cities: `sfbay`, `newyork`,
   `london`, or `SF bay area`, `new york`, `vancouver, BC`.
2. Set **Section** to a section code or name, for example `sss` (for sale),
   `cta` (cars+trucks), `apa` (apartments / housing for rent), `jjj`
   (jobs). The source requires a section, so leaving it alone uses `sss`.
3. Optionally add a **Keyword**, a price range, **Posted by**, **Only
   postings with photos**, **Only postings from today** and a **Sort by**
   order.
4. Set **Max items**. With several cities, each gets an equal share and the
   last one takes the remainder.

#### Craigslist links (URL mode)

Set **Mode** to `Craigslist links` and paste any mixture of:

- search links, per city: `https://sfbay.craigslist.org/search/cta?query=truck&min_price=5000`
- search links, shared form: `https://www.craigslist.org/search/area/newyork?cat=apa&max_price=3000`
- posting links: `https://www.craigslist.org/view/d/<slug>/<id>`
- classic posting links: `https://sfbay.craigslist.org/eby/bik/d/<slug>/0000000000.html`

A search link re-runs that exact search, keeping the query written into the
link. The city-search filter fields are not applied to a pasted link. A
posting link is read in full.

### Input Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `mode` | select | `search` (cities + section + filters) or `url` (paste craigslist links). Default `search`. |
| `cities` | array | City search: one or more craigslist cities, as a site name (`sfbay`), a display name (`SF bay area`) or any link from that city. Default `["sfbay"]`. |
| `category` | string | City search: the section to read, by code or by name. Default `sss` (for sale). The source requires a section, so this is never empty. |
| `query` | string | City search: keyword to match in the posting. Leave empty to browse the whole section. |
| `minPrice` | integer | City search: lowest price to return, in the city's own currency. |
| `maxPrice` | integer | City search: highest price to return, in the city's own currency. Setting either bound drops postings that carry no price. |
| `soldBy` | select | City search: `all`, `owner` or `dealer`. Default `all`. |
| `hasImage` | boolean | City search: keep only postings that carry at least one photo. Default `false`. |
| `postedToday` | boolean | City search: keep only postings published today. Default `false`. |
| `sortBy` | select | City search: `rel`, `date`, `dateoldest`, `priceasc` or `pricedsc`. Default `rel`. |
| `startUrls` | array | URL mode: craigslist search links or posting links, in either the per-city or the shared form. |
| `urls` | array | Alias for `startUrls`, accepted under the name other actors use for URL mode. |
| `maxItems` | integer | THE run cap: how many records to return. Default `20`; `0` means no limit. With several cities each gets an equal share and the last takes the remainder. |
| `fetchDetails` | boolean | Off by default. Also read every posting in full (posting text, attribute list, display address, update time). Adds one Posting detail event per posting read. |
| `resumeFromRunId` | string | Continue ONE interrupted run: paste its run ID or dataset ID and postings already collected there are skipped. For recurring monitoring use `incrementalMode` instead. |
| `incrementalMode` | boolean | Off by default. Turn on for daily or recurring monitoring of the same search: the first run returns everything as `NEW`, later runs return only `NEW`, `UPDATED` and `REAPPEARED`, each carrying `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt`. |
| `stateKey` | string | Optional, incremental mode only. Name a monitoring campaign to keep its state stable, or deliberately share it. Leave empty to derive the key from the search and filter settings. |
| `emitUnchanged` | boolean | Off by default, incremental mode only. Also return postings unchanged since the last run, marked `UNCHANGED` (those rows are billed too). |
| `emitExpired` | boolean | Off by default, incremental mode only. Also return tracked postings no longer found, marked `EXPIRED`. Produced only after a complete, uncapped, non-resumed scan. |
| `mcpConnectors` | array | Optional MCP connectors to pipe a copy of each posting into. Authorize one under Apify, Settings, API & Integrations, then pick it here. Leave empty to skip. |
| `notionParentPageUrl` | string | Notion only: URL or id of the page under which posting pages are created. Required to enable the Notion export; ignored by other connectors. |
| `maxNotifyListings` | integer | Cap on postings written to each connector per run. Default `50`. Does not affect the dataset. |
| `proxyConfiguration` | object | Apify Proxy settings. Works on every Apify plan, including the free one. |

### Cities

Cities are recognised three ways, all case and punctuation insensitive:

- **site name**, the label in the address: `sfbay`, `newyork`,
  `losangeles`, `chicago`, `seattle`, `london`, `toronto`, `vancouver`,
  `berlin`, `paris`, `sydney`, `tokyo`, `mexicocity`, `delhi`
- **display name**, as craigslist writes it: `SF bay area`, `new york`,
  `los angeles`, `vancouver, BC`
- **any link from that city**, which is the easiest way to be sure

707 cities across 79 countries are shipped. The United States has 413,
Canada 55, the United Kingdom 27, India 16, Mexico 16, Germany 15, France
13, Australia 12, China 12, Italy 11, Spain 11 and so on. An unrecognised
city is reported in the log and skipped; if none resolves, the run fails
with the list rather than returning nothing.

### Sections

Eight top sections cover the whole network:

| Code | Section |
|---|---|
| sss | for sale |
| hhh | housing |
| jjj | jobs |
| ggg | gigs |
| bbb | services |
| ccc | community |
| eee | events |
| rrr | resumes |

A section is always sent, because the source refuses a search that names
none. Leave **Section** alone and `sss` is used.

Narrower codes work too, for example `cta` cars+trucks, `apa` apartments /
housing for rent, `roo` rooms & shares, `bia` bikes, `ela` electronics,
`fua` furniture, `sof` software / QA / DBA, `web` web / info design, `cpg`
computer gigs. A code that ends in `o` is usually the owner-only variant
and one ending in `d` the dealer-only variant; prefer the wider code and
use **Posted by** to narrow, so the filter shows up in your input rather
than being hidden in a code. 228 section codes are shipped, and section
names such as `cars+trucks` or `for sale` resolve as well as codes.

### Recurring monitoring

Turn on **Incremental changes for scheduled runs** and schedule the actor.
The first run returns every match as `NEW`. Later runs return only what
changed:

| changeType | Meaning |
|---|---|
| NEW | first time this posting was seen for this campaign |
| UPDATED | the posting changed; `changedFields` lists what |
| UNCHANGED | nothing changed (suppressed unless you turn on Emit unchanged) |
| REAPPEARED | a posting that had gone came back |
| EXPIRED | a tracked posting is gone (only after a complete scan, and only with Emit expired on) |

State is kept separately for each city, section, filter and link setup, so
two different searches never share a baseline. Name a campaign with **State
key** to keep its state stable, or to share it deliberately.

`EXPIRED` is only produced when a run scanned the whole tracked search: not
resumed, not stopped by Max items, no search or posting read left
unfinished, and at least one record actually seen. A partial run never
marks live postings gone.

### Resume

Paste a previous run ID or dataset ID into **Resume from a previous run**
to continue a large pull without returning postings already collected
there.

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

Optionally pipe results into the apps you already use through Model Context
Protocol (MCP) connectors. This is an extra delivery step **after** the
scrape: the Apify dataset is never changed, delayed or reduced, and a
connector failure never fails the run.

1. Authorize a connector once under **Apify, Settings, API & Integrations**.
2. List its id in **`mcpConnectors`**. If the picker is empty, no connector
   is authorized yet.
3. For Notion, also set **`notionParentPageUrl`** to the page under which
   posting pages are created. Other connectors get a best-effort record
   write or a digest message.
4. Use **`maxNotifyListings`** to cap how many postings are written to each
   connector per run.

The connection is mediated by Apify's MCP proxy, so this actor never sees
your third-party credentials. Leave `mcpConnectors` empty to skip.

### What you are charged for

The Store page shows the current rates. Three things are counted:

- **Actor Start**, once per run.
- **Posting record**, one per posting returned in the dataset.
- **Posting detail**, one per posting read in full. Rows that were not read
  in full never carry it, and a posting read that fails still ships the
  base record without it.

### Limits and honest caveats

- A craigslist search returns what the source itself matches. The largest
  single result set observed was about 9,000 postings; the source does not
  offer more of one search than that.
- Setting a price bound drops postings that carry no price at all, because
  the source filters them out server side. That is the source's behaviour,
  not a client-side choice.
- Sub-area narrowing (for example east bay inside SF bay area) is not
  offered: the source accepts the parameter and then ignores it, so an
  input for it would silently do nothing. Sub-area is still reported on
  every record, so you can filter it yourself.
- Photos are returned as addresses, not downloaded.
- The actor does not post, reply, flag or log in. It reads public pages only.

# Actor input Schema

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

Search reads postings from the cities and section you pick. URL mode reads the exact craigslist links you paste.

## `cities` (type: `array`):

City search: one or more craigslist cities, by site name (sfbay, newyork, london), by display name (SF bay area, new york, vancouver, BC) or by pasting any link from that city. 700+ cities across 79 countries are recognised. See the README for how cities are named.

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

City search: the craigslist section to read, by code or by name. The source requires a section, so this is never empty: leave it alone and the for sale section (sss) is used. Whole sections: sss (for sale), hhh (housing), jjj (jobs), ggg (gigs), bbb (services), ccc (community), eee (events), rrr (resumes). Narrower examples: cta (cars+trucks), apa (apartments / housing for rent), bia (bikes), ela (electronics), fua (furniture), sof (software / QA / DBA).

## `query` (type: `string`):

City search: keyword to match in the posting. Leave empty to browse the whole section.

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

City search: lowest price to return, in the city's own currency. Postings with no price are not returned once a price bound is set.

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

City search: highest price to return, in the city's own currency. Postings with no price are not returned once a price bound is set.

## `soldBy` (type: `string`):

City search: keep only owner postings or only dealer postings. Default keeps both.

## `hasImage` (type: `boolean`):

City search: keep only postings that carry at least one photo.

## `postedToday` (type: `boolean`):

City search: keep only postings published today. Useful for a scheduled run that watches a busy section.

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

City search: the order the source returns matches in. Newest first is the usual choice for monitoring; relevance is the source default for a keyword search.

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

URL mode: craigslist search links (https://sfbay.craigslist.org/search/cta?query=truck) or posting links (https://www.craigslist.org/view/d/.../..., or a classic https://sfbay.craigslist.org/eby/bik/d/.../0000000000.html link). Multi-value supported.

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

Alias for Craigslist links, accepted under the name other Apify actors use for URL mode. Each entry is a search link or a posting link; entries are read in order.

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

Stop after this many records (0 = no limit; the run then returns every posting the searches matched).

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

Off by default. Turn on to also read every posting in full: the posting text, the attribute list (bedrooms, condition, make and model, fuel, compensation and so on), the display address and the update time. Each read adds one Posting detail event. Posting links pasted in URL mode are always read in full and always charge this event.

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

Paste a previous run ID or dataset ID to continue a large pull without returning postings already collected there.

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

Turn this on for daily or recurring monitoring. The first run returns every matching posting as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED postings. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately for each city, section, filter and link setup; use State key to name or deliberately share a monitoring campaign.

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

Optional. Name this monitoring campaign to keep its state stable, or deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the search and filter settings.

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

Off by default. Turn on to also return postings that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have.

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

Off by default. Turn on to also return postings that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search.

## `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 rich page-per-item export; other connectors get a best-effort write or digest. Leave empty to skip; never changes the dataset output.

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

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

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

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

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

Apify Proxy settings. Works on every Apify plan, including the free one.

## Actor input object example

```json
{
  "mode": "search",
  "cities": [
    "sfbay"
  ],
  "category": "sss",
  "query": "bicycle",
  "soldBy": "all",
  "hasImage": false,
  "postedToday": false,
  "sortBy": "rel",
  "startUrls": [
    "https://sfbay.craigslist.org/search/bia?query=bicycle"
  ],
  "maxItems": 20,
  "fetchDetails": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (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",
    "cities": [
        "sfbay"
    ],
    "category": "sss",
    "query": "bicycle",
    "soldBy": "all",
    "hasImage": false,
    "postedToday": false,
    "sortBy": "rel",
    "startUrls": [
        "https://sfbay.craigslist.org/search/bia?query=bicycle"
    ],
    "maxItems": 20,
    "fetchDetails": false,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "maxNotifyListings": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/craigslist-classifieds-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",
    "cities": ["sfbay"],
    "category": "sss",
    "query": "bicycle",
    "soldBy": "all",
    "hasImage": False,
    "postedToday": False,
    "sortBy": "rel",
    "startUrls": ["https://sfbay.craigslist.org/search/bia?query=bicycle"],
    "maxItems": 20,
    "fetchDetails": False,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "maxNotifyListings": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/craigslist-classifieds-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",
  "cities": [
    "sfbay"
  ],
  "category": "sss",
  "query": "bicycle",
  "soldBy": "all",
  "hasImage": false,
  "postedToday": false,
  "sortBy": "rel",
  "startUrls": [
    "https://sfbay.craigslist.org/search/bia?query=bicycle"
  ],
  "maxItems": 20,
  "fetchDetails": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/craigslist-classifieds-scraper --silent --output-dataset

```

## MCP server setup

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