# Craigslist Scraper — Listings, Prices & New Post Alerts (`tortuga/craigslist-scraper`) Actor

Returns Craigslist listings from any search URL or city + category: title, price, location, coordinates, posted date, images, bedrooms / sqft, job pay, category; optional full description and attributes. Multi-city ('all US'), filters, and new-post monitoring.

- **URL**: https://apify.com/tortuga/craigslist-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** E-commerce, Real estate, Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 listing scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Craigslist Scraper — Listings, Prices & New Post Alerts

Scrape Craigslist listings from any city, category or search URL: title, price, location and neighbourhood, map coordinates, posted date, category, post id and URL, every photo, bedrooms and square feet for housing, compensation, job title and company for jobs and gigs, odometer for cars. Turn on posting details for the full description and every attribute (condition, make / model, VIN, employment type, rent period, bathrooms, laundry, parking...). Works for for sale, housing, jobs, gigs, services, community and events, on every Craigslist site worldwide, one city or **all US** at once.

Paste the Craigslist search URL you see in your browser, or pick sites, a category, keywords and filters, and get clean JSON, CSV or Excel, or the data straight into your app through the API. Schedule it with **Only new or changed items** and it becomes a **Craigslist alert**: each run returns only postings that are new since the last run (or whose price dropped), and you pay nothing for the ones you already have.

It reads the same JSON search API the Craigslist website itself uses, so one request returns up to 10,000 postings with coordinates and photos, runs take seconds, and nothing breaks when the page layout changes. You pay only for the listings you get.

### What data does Craigslist Scraper extract?

| Field | Description |
|---|---|
| `id`, `uuid`, `url` | Craigslist post id (as shown at the bottom of a posting), the permanent posting key and the posting URL |
| `title` | Posting title |
| `price`, `priceText`, `currency` | Numeric price, the text as shown ("$2,750") and the site's currency (USD, CAD, GBP, ...). Jobs, gigs, services and community posts usually have no price. |
| `postedAt` | Date shown in search results (ISO 8601, UTC). Craigslist sorts by it; a renewed posting gets a new date. |
| `category`, `categoryName`, `section` | Category code and name (`apa` = "apartments / housing for rent", `cto` = "cars & trucks - by owner") and the section: for sale, housing, jobs, gigs, services, community, events |
| `site`, `subarea`, `location`, `neighborhood` | Craigslist site (`sfbay`, `newyork`), subarea (`eby`, `brk`), the location text the poster gave ("sunset / parkside", "Bronx") and the mapped neighbourhood |
| `latitude`, `longitude` | Map coordinates (present on nearly every posting) |
| `images`, `imageCount` | All photo URLs in 1200x900 |
| `bedrooms`, `sqft`, `openHouseDates` | Housing |
| `compensation`, `jobTitle`, `company` | Jobs and gigs |
| `odometer`, `monthlyPayment` | Cars and trucks (miles; dealer financing text) |
| `eventStartDate`, `eventEndDate`, `eventDates` | Events and classes |
| `searchUrl`, `scrapedAt` | The search the posting came from and the scrape time |

Turn on **Include posting details** to also open every posting page and add:

| Field | Description |
|---|---|
| `description` | Full posting text (plain text, line breaks kept) |
| `attributes` | Every labelled attribute as `{label: value}`, e.g. `{"condition": "excellent", "odometer": "112,000", "VIN": "2T3RFREV3JW822836", "transmission": "automatic"}` or `{"employment type": "full-time", "compensation": "Starting $17.27/hr + tips"}` |
| `attributeTags` | Unlabelled attributes, e.g. `["2BR / 1.5Ba", "available now", "cats are OK - purrr", "w/d in unit", "carport"]` |
| `bathrooms`, `availableFrom`, `rentPeriod`, `sqft` | Housing |
| `employmentType`, `compensation`, `jobTitle` | Jobs |
| `year`, `makeModel`, `make`, `model`, `condition`, `odometer`, `vin` | Cars and goods |
| `firstPostedAt`, `updatedAt` | When the posting was first published and last edited |
| `city`, `region`, `postalCode`, `country` | Locality from the posting's map |
| `streetAddress` | Apartments for rent, office / commercial, parking, broker real estate and job posts only: the advertised property or workplace as published (often cross streets) |
| `notices` | Poster notices such as "Principals only. Recruiters, please don't contact this job poster." |
| `isRemoved` | `true` if the posting was deleted or expired between search and detail fetch |

### How to scrape Craigslist listings

1. **By URL:** open Craigslist, run your search with any filters (price, bedrooms, make, "has image", "posted today", miles from ZIP) and copy the URL from the address bar into **Start URLs**, e.g. `https://sfbay.craigslist.org/search/sss?query=road+bike&min_price=100&max_price=900`. Subarea URLs (`/search/eby/apa`), city URLs and `https://www.craigslist.org/search/area/sfbay?cat=sss` work too.
2. **Or by site + category:** put one or more sites in **Sites** (`sfbay`, `newyork`, `losangeles`, `chicago`, `seattle`, `sfbay/eby` for a subarea, or `all US`), pick a **Category** (for sale, housing, jobs, cars & trucks, apartments, gigs, services, free stuff, ...) and optionally a **Search query** and filters.
3. Set **Max items** (cost cap) and, for multi-city runs, **Max items per search / site**. Optionally turn on **Include posting details**.
4. Click **Start**. Export the dataset as JSON, CSV or Excel, or read it through the API.

A posting URL (`https://www.craigslist.org/view/d/…` or the classic `https://sfbay.craigslist.org/…/7976426511.html`) returns that one posting with full details.

### Input examples

2-bedroom apartments under $4,000 with photos in Brooklyn, newest first:

```json
{
  "startUrls": [{ "url": "https://newyork.craigslist.org/search/brk/apa?min_bedrooms=2&max_price=4000&hasPic=1&sort=date" }],
  "maxItems": 500
}
```

Kayaks for sale in every US city, at most 20 per city:

```json
{
  "sites": ["all US"],
  "category": "sss",
  "searchQuery": "kayak",
  "maxItemsPerSearch": 20,
  "maxItems": 5000
}
```

Food & hospitality jobs in three cities with the full job description:

```json
{
  "startUrls": [
    { "url": "https://losangeles.craigslist.org/search/fbh" },
    { "url": "https://sandiego.craigslist.org/search/fbh" },
    { "url": "https://sfbay.craigslist.org/search/fbh" }
  ],
  "maxItems": 300,
  "includeDetails": true
}
```

### Output example

```json
{
  "id": "7980953976",
  "uuid": "tBDvuCQRZG3J2TwxuA5JNz",
  "url": "https://www.craigslist.org/view/d/bronx-apartment-for-rent/tBDvuCQRZG3J2TwxuA5JNz",
  "title": "Apartment for rent",
  "price": 2750,
  "priceText": "$2,750",
  "currency": "USD",
  "postedAt": "2026-10-09T23:14:39Z",
  "category": "apa",
  "categoryName": "apartments / housing for rent",
  "section": "housing",
  "site": "newyork",
  "subarea": "brx",
  "location": "Bronx",
  "neighborhood": null,
  "latitude": 40.8713,
  "longitude": -73.8435,
  "images": [
    "https://images.craigslist.org/00V0V_9BJRRMqDura_0rw0kD_1200x900.jpg",
    "https://images.craigslist.org/00W0W_7P7NYatL1L1_0kE0fu_1200x900.jpg",
    "https://images.craigslist.org/00h0h_pXDD7Bw8rh_0rw0kD_1200x900.jpg"
  ],
  "imageCount": 3,
  "bedrooms": 3,
  "searchUrl": "https://newyork.craigslist.org/search/apa?hasPic=1&max_price=4000&min_bedrooms=2&sort=date",
  "scrapedAt": "2026-10-10T00:20:31+00:00",
  "description": "3 BR apartment, recent renovated, close to Supermarket and No 5 Subway Station, $2750 per month plus charges for electricity and cooking gas. No fees.",
  "attributes": { "rent period": "monthly" },
  "attributeTags": ["3BR / 1Ba", "apartment", "no laundry on site", "no parking"],
  "rentPeriod": "monthly",
  "bathrooms": 1,
  "firstPostedAt": "2026-10-09T23:14:39Z",
  "city": "Bronx",
  "region": "NY",
  "postalCode": "10469",
  "country": "US",
  "streetAddress": "Hammersley Ave, Bronx, NY 10469"
}
```

The fields from `description` on come from **Include posting details**. A job posting has `compensation`, `jobTitle`, `company` (and with details `employmentType`); a car has `odometer` (and with details `year`, `makeModel`, `vin`, `condition`, `attributes`).

### Monitor changes on a schedule (Craigslist alerts)

Turn on **Only new or changed items** and run the same search on a schedule: each run remembers what earlier runs returned and outputs only postings that are new, plus postings whose price (or job pay) changed. Unchanged postings are dropped before any posting page is opened, so they are not charged — a quiet hour costs next to nothing. **Max items** becomes the number of newest postings checked per run (keep **Sort** on *Newest first*). Save the input as a task, add a Schedule in Apify (every 15 minutes, hourly or daily) and connect an integration (email, Slack, Zapier, Make, a webhook) to get every new apartment, free couch or underpriced bike the moment it is posted; give each watch its own **Monitoring key** if several schedules use the same search.

Every output item has `changeType` (`new` or `changed`); changed items have `previousValues` with the old values, e.g. `{"price": 3000}` when the rent dropped to $2,750. Craigslist lets posters *renew* a listing, which moves an older posting back to the top; the Actor recognises those from the post id (`isRenewal: true`), and **Skip renewed older posts** leaves them out entirely, so your alerts carry brand-new posts only (skipped renewals are not charged either). A posting you were already alerted about is never reported again just because it was renewed. Monitoring runs always ask Craigslist for a fresh answer (normal searches may be served from Craigslist's own cache, up to 15 minutes old, exactly as the website shows them).

```json
{
  "startUrls": [{ "url": "https://seattle.craigslist.org/search/zip?sort=date" }],
  "maxItems": 50,
  "onlyNewOrChanged": true,
  "skipRenewals": true
}
```

### How many listings can I get per search?

Craigslist returns at most 10,000 postings per search (the site itself shows no more). The Actor fetches them in one request. For bigger result sets, split the search: by subarea (`/search/brk/apa`, `/search/que/apa`), by category (`cto` and `ctd` instead of `cta`), or by price range. Several searches and sites run in one run and are de-duplicated by post id. When a small site has few results, Craigslist appends postings from nearby sites; those are left out (search those sites directly or use `all US`).

### Can I search all of Craigslist (all US cities)?

Yes. Put `all US` in **Sites** (413 US sites; `all CA`, `all GB` etc. work for other countries) or list the cities you need. Use **Max items per search / site** so Max items is spread across cities, e.g. 20 per city. An `all US` sweep makes one request per site and takes a few minutes. Alternatively, set **ZIP / postal code** and **Distance** up to 1,000 miles to search a whole region in a single request.

### Does it collect phone numbers or email addresses?

No. Craigslist hides contact details behind its "reply" button (anonymised relay e-mail, phone on request); the Actor never opens it. It exports the public posting only: text, price, photos, attributes, coordinates and the location the poster chose to show. Street addresses are exported only where they are the advertised rental, commercial property or workplace (apartments for rent, office / commercial, parking, broker listings, jobs); rooms, sublets and by-owner home sales, which are usually the poster's own home, never carry one.

### Do I need a proxy?

Apify Proxy (datacenter) is on by default and is enough: every request goes out through a fresh IP, the Actor keeps a modest pace and retries on another IP if Craigslist refuses one. If a run logs many blocked (403) requests, switch to the RESIDENTIAL group. If the search API ever fails, the Actor falls back to Craigslist's plain HTML search page (first ~350 results, without post ids and dates).

### Pricing

Pay per result: a small fee for every listing saved, plus a small fee per posting page opened when **Include posting details** is on (see the Pricing tab). No subscription, no start fee; Apify's free plan is enough to try it. Platform usage is tiny: about $0.0002 per 1,000 listings from search results and about $0.02 per 1,000 posting pages.

### Integrations and API

Use the runs in Zapier, Make, n8n, Google Sheets or Slack, or call the Actor from Python / Node with the Apify client. See the **API** tab for ready-made snippets.

### Is it legal to scrape Craigslist?

This Actor collects publicly visible posting data only: no logins, no hidden contact details. You are responsible for how you use the data and for complying with Craigslist's terms of use, privacy law (GDPR, CCPA) and other applicable law; do not use it to spam posters.

### Support

Found a bug or need a field added? Open an issue in the **Issues** tab; it is usually answered within a day.

# Actor input Schema

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

Craigslist search pages with any filters set in the browser, e.g. https://sfbay.craigslist.org/search/sss?query=bike\&min_price=100 (any site, subarea such as /search/eby/apa, or category). A posting URL (https://www.craigslist.org/view/d/…) returns that one posting with full details. Clear this field if you use Sites + Category instead.

## `sites` (type: `array`):

Instead of Start URLs: Craigslist sites to search with the category and filters below, i.e. the subdomain of https://<site>.craigslist.org, e.g. sfbay, newyork, losangeles, chicago, seattle. Use 'sfbay/eby' for a subarea, or 'all US' (413 sites), 'all CA', 'all GB' for every site in a country.

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

Used with Sites. For any other category, paste its search URL into Start URLs (the code after /search/, e.g. /search/ppa = appliances).

## `searchQuery` (type: `string`):

Keywords, as typed into Craigslist's search box, e.g. "road bike", "2 bedroom", "forklift". Craigslist operators work: -word excludes, "exact phrase", word1|word2.

## `searchTitlesOnly` (type: `boolean`):

Match the query against posting titles only (Craigslist's 'search titles only').

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

Used with Sites. In the site's currency (USD in the US).

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

Used with Sites.

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

Only postings from today.

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

Only postings with at least one photo.

## `bundleDuplicates` (type: `boolean`):

Let Craigslist hide near-identical reposts (its 'bundle duplicates' option).

## `sellerType` (type: `string`):

For-sale categories: by owner or by dealer only.

## `minBedrooms` (type: `integer`):

Housing categories.

## `maxBedrooms` (type: `integer`):

Housing categories.

## `minSqft` (type: `integer`):

Housing categories.

## `maxSqft` (type: `integer`):

Housing categories.

## `postalCode` (type: `string`):

Search around a ZIP code instead of the whole site (use with Distance). A large distance (up to 1000 miles) searches across neighbouring sites in one request.

## `searchDistance` (type: `integer`):

Radius around the ZIP / postal code in miles (1-1000).

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

Used with Sites (start URLs keep their own sort). 'Newest first' is the right choice for monitoring and complete crawls.

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

Stop after this many postings in total (across all searches and sites). Craigslist returns at most 10,000 postings per search; split bigger searches by subarea or price range.

## `maxItemsPerSearch` (type: `integer`):

Optional cap per search URL or site, so a multi-city run (e.g. 'all US') spreads Max items over many cities instead of filling up from the first ones.

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

Also open every posting page for the full description, all attributes (condition, make / model, VIN, odometer, employment type, rent period, laundry, parking...), bathrooms, first posted and updated dates, city / ZIP, and the street address for rental and job posts. One extra request per posting, charged extra (see pricing).

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. Here: new postings, and postings whose price or job pay changed. Max items is the number of newest postings checked per run.

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input

## `skipRenewals` (type: `boolean`):

Monitoring only. Craigslist lets posters renew a listing, which moves an older posting back to the top. With this on, such postings are not output (they are remembered and not charged), so alerts contain brand-new posts only. Off: they are output with isRenewal: true.

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

Apify Proxy (datacenter) works for Craigslist and is the default; each request goes out from a fresh IP. Switch to the RESIDENTIAL group only if a run logs many blocked (403) requests.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://sfbay.craigslist.org/search/sss?query=road+bike&sort=date"
    }
  ],
  "category": "sss",
  "searchTitlesOnly": false,
  "postedToday": false,
  "hasImage": false,
  "bundleDuplicates": false,
  "sellerType": "",
  "sort": "date",
  "maxItems": 100,
  "includeDetails": false,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "startUrls": [
        {
            "url": "https://sfbay.craigslist.org/search/sss?query=road+bike&sort=date"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/craigslist-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 = {
    "startUrls": [{ "url": "https://sfbay.craigslist.org/search/sss?query=road+bike&sort=date" }],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/craigslist-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 '{
  "startUrls": [
    {
      "url": "https://sfbay.craigslist.org/search/sss?query=road+bike&sort=date"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call tortuga/craigslist-scraper --silent --output-dataset

```

## MCP server setup

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