# Craigslist Listings Scraper (`axiomworks/craigslist-listings-scraper`) Actor

Search public Craigslist listings by city, category, and optional keywords, then collect listing IDs, titles, prices, dates, coordinates, neighborhoods, images, and source URLs. Optional detail-page requests add available descriptions and attributes to the dataset.

- **URL**: https://apify.com/axiomworks/craigslist-listings-scraper.md
- **Developed by:** [Axiom Works](https://apify.com/axiomworks) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.37 / 1,000 results

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

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Craigslist Listings Scraper

**Returns:** one row per Craigslist listing with id, sourceUrl, title, price, postedAt, city, category, neighborhood, bedrooms, bathrooms, squareFeet.
**Input:** `{"cities": ["sfbay"], "category": "apa", "searchQuery": "studio", "searchQueries": ["1 bedroom"], "maxItems": 50}`
**Best for:** apartment and rental monitoring, used-goods price tracking, multi-keyword lead lists.

```json
{"id": "7964382795", "title": "Vintage Cannondale Road Bike “Made in the U.S.A”", "price": null, "postedAt": "2026-10-15T03:37:43.000Z", "city": "sfbay", "category": "bicycles - by owner", "categoryAbbr": "bik", "neighborhood": "hayward / castro valley", "imageCount": 8}
```

Empty or invalid targets (unknown city, unknown category, no matches) end the run successfully with 0 results and a status message saying what to change.

Collect public Craigslist search listings from one or more cities into a structured dataset. Select a category, add search words if useful, and set an exact output limit. The Actor reads the public search result feed, decodes listing information, and can visit individual public listing pages for descriptions and attributes. Each result includes a stable posting ID and a public source URL. No Craigslist login is needed.

### What does Craigslist Listings Scraper do?

Craigslist Listings Scraper searches the public Craigslist listings feed for each city you supply. It recognizes city hostnames such as `sfbay`, `austin`, and `newyork`, along with Craigslist search URLs. It resolves city and category identifiers using Craigslist's public reference catalogs, then requests search pages in date order. The Actor decodes the compact response into ordinary records suitable for spreadsheets, databases, alerts, and analysis.

The search can cover a broad category, such as all for sale, or a narrower category such as bicycles, cars, jobs, or apartments. `searchQuery` further narrows the result set. Price and age filters are applied to decoded listings before output. Results from several cities share one dataset, and posting IDs are deduplicated across the run. `maxItems` applies to the entire run, not separately to each city.

By default (`includeDetails` is true) the Actor also requests each public listing page to extract its description and displayed attributes. This increases requests and run time. Detail pages can disappear after the search response is generated; in that case the listing is skipped rather than emitted without a description. The Actor never follows reply or account flows and does not collect contact information.

### What data can you get?

| Field | Meaning |
| --- | --- |
| `id` | Stable Craigslist posting identifier. |
| `sourceUrl` | Public listing URL built from the feed's slug and UUID. |
| `title` | Listing headline. |
| `price` | Numeric asking price, when present. |
| `priceText` | Asking price as shown in the search feed. |
| `postedAt` | Posting time in UTC ISO format. |
| `city` | Craigslist city hostname used for the search. |
| `category` | Category name from the reference catalog. |
| `categoryAbbr` | Category abbreviation for that listing. |
| `latitude`, `longitude` | Approximate feed coordinates, when supplied. |
| `neighborhood` | Neighborhood or location description, when supplied. |
| `imageCount` | Count of image identifiers in the feed. |
| `imageUrls` | Image URLs derived from those identifiers. |
| `description` | Public page description when details are enabled and available. |
| `attributes` | Public page attribute labels and values when available. |
| `attributeFlags` | Unlabelled page attributes, such as “cryptocurrency ok” or “delivery available”, copied verbatim when details are enabled; otherwise null. |
| `dealSignal` | Optional AI judgment: `underpriced`, `fair`, `overpriced`, or `unknown`. |
| `matchedQuery` | Search keyword that found the listing. |
| `bedrooms` | Bedroom count from the listing page, null if not stated. |
| `bathrooms` | Bathroom count from the listing page, null if not stated. |
| `squareFeet` | Floor area in ft2 from the listing page, null if not stated. |

Values unavailable in a particular listing are returned as `null` or, for images, an empty array. Coordinates are approximate listing location data rather than a verified street address. A category requested for the search is a search scope: the output `categoryAbbr` names the actual category of each result, which can be narrower. The Actor preserves the public listing ID so repeated runs can be compared without relying on changing titles or prices.

Recent additions: `searchQueries` runs several keywords per city in one run, `matchedQuery` records the keyword that found each listing, and `bedrooms`, `bathrooms` and `squareFeet` are parsed from the listing page when `includeDetails` is true and the listing states them (otherwise null).

### How to use

Enter one or more city hostnames in `cities`. Add a category abbreviation in `category`, such as `sss` for all for sale, `bia` for bicycles, `cta` for cars and trucks, `apa` for apartments, or `jjj` for jobs. Add `searchQuery` when you need particular words. Set `maxItems` to the exact maximum number of unique records you want. The default limit is 50.

Run the Actor and open its dataset. The overview table exposes the ID, title, price, posted time, city, category, neighborhood, image count, source URL, and AI signal. Export the dataset using Apify's normal dataset formats or retrieve it through the API. When `includeDetails` is false, descriptions and attributes are empty and runs are faster.

For monitoring, combine a specific city and category with `postedWithinHours`. Search results are sorted newest first unless you set `sortBy`. The Actor stops paging when it reaches older records. For research across cities, list several city hostnames and leave enough room in `maxItems` for all of them. The city order is respected, so a full first city may consume the limit before the next city is reached. Narrow the search or run cities separately if you need an equal sample from each one.

The Actor uses modest pacing between search pages and detail requests. It retries transient blocks with backoff and a fresh proxy session. Public listings can be edited or deleted at any time, so a source URL may cease to load after it was returned. For an invalid city, the Actor warns and continues with the remaining cities. A search with no matches completes with zero dataset items and a warning.

### Input

| Input | Required | Default | Description |
| --- | --- | --- | --- |
| `cities` | Yes | Prefill: `sfbay` | Array of city hostnames or Craigslist search URLs. |
| `category` | No | `sss` | Search category abbreviation. |
| `searchQuery` | No | Empty | Search words. |
| `searchQueries` | No | Empty | Extra search words run in every city besides `searchQuery`; results are de-duplicated across queries and each record's `matchedQuery` shows which keyword found it. |
| `maxItems` | No | 50 | Maximum unique output records across all cities. |
| `minPrice` | No | Unset | Keep priced listings at or above this amount. |
| `maxPrice` | No | Unset | Keep priced listings at or below this amount. |
| `postedWithinHours` | No | Unset | Keep only recent posts and stop paging after older ones. |
| `sortBy` | No | `newest` | `newest`, `relevance`, `priceAsc` or `priceDesc`. Use `priceAsc` or `priceDesc` with `maxItems` to get the cheapest or most expensive listings in each city. |
| `includeDetails` | No | `true` | Request each public detail page for description and attributes. |
| `enrichWithAI` | No | false | Adds an AI deal signal to each listing. Costs extra per enriched result. When false, `dealSignal` is null and no AI call is made. |
| `maxJevCalls` | No | 500 | Upper limit on AI calls in the run. Only used when `enrichWithAI` is true. |
| `proxyConfiguration` | No | Apify proxy | Proxy settings for the target requests. |

Example input:

```json
{
  "cities": ["sfbay", "austin"],
  "category": "bia",
  "searchQuery": "road bike",
  "maxItems": 30,
  "minPrice": 100,
  "maxPrice": 2000,
  "includeDetails": true
}
```

The price filter uses the numeric price provided by Craigslist. Listings without a numeric price are not excluded by that filter. Search terms and category filters are sent to the public search endpoint. The time and price filters are then checked by the Actor before it pushes a record. If a category abbreviation is invalid, the Actor fails with a clear error rather than silently switching categories.

The optional AI processor is TypeSafe Jev, a third-party service. When you set `enrichWithAI` to true, Jev judges whether the listing appears underpriced, fairly priced, overpriced, or unknown from its title, price, category, and available attributes. This is an estimate, not a verified market valuation. On an AI service error or rate limit, the listing is still returned and `dealSignal` stays empty. AI processing does not change the source fields.

### Output

This example is an excerpt of a real item from a local prefill run. The listing is public and may later be removed or edited:

```json
{
  "id": "7973855220",
  "sourceUrl": "https://www.craigslist.org/view/d/menlo-park-raleigh-capri-30-road-bike/196JW6rPSXf8LEpYRAMKwa",
  "title": "Raleigh Capri 3.0 road bike (105)",
  "price": 400,
  "postedAt": "2026-10-01T20:19:06.000Z",
  "city": "sfbay",
  "category": "bicycles - by owner",
  "categoryAbbr": "bik",
  "latitude": 37.4379,
  "longitude": -122.181,
  "neighborhood": null,
  "imageCount": 10,
  "imageUrls": ["https://images.craigslist.org/01515_kF0wPOcCGOa_0CI0t2_600x450.jpg"],
  "description": null,
  "attributes": null,
  "dealSignal": null,
  "matchedQuery": "road bike",
  "bedrooms": null,
  "bathrooms": null,
  "squareFeet": null
}
```

The example shortens `imageUrls` for readability; the actual record contains all ten image URLs. `postedAt` is normalized to UTC. The Actor does not infer missing prices, addresses, or seller details. In the default mode, `sourceUrl` is built from data in the search response; detail page fetching is optional.

### How much does it cost?

This Actor is charged per result. Each pushed listing is one `result` event. A listing with successful Jev enrichment also has one `enriched-result` event, and only when `enrichWithAI` is true; it is off by default. Review the Actor's Pricing tab for current terms before running a large collection. Set `maxItems` to control the maximum number of results, and use filters to narrow the run. The Actor does not charge a result event for skipped duplicates, filtered listings, empty searches, or errors.

### Use with AI agents (MCP)

Add Apify's MCP server to an AI client with this server URL:

```text
https://mcp.apify.com/?tools=axiomworks/craigslist-listings-scraper
```

The Actor's input and dataset schemas describe the available parameters and output fields to the client. Give the agent a city, a category, and a result limit so its request stays specific. Example prompts:

- Find recent public road bike listings in the `sfbay` Craigslist city. Return title, price, posting time, and source URL for up to 20 listings.
- Collect public car listings from `austin` with the `cta` category and show ID, title, price, city, and source URL for up to 15 records.
- Compare bicycle listing titles, prices, and posting times from `sfbay` and `austin` using the `bia` category and a 30 item limit.

The AI client receives structured data from the Actor. It should treat `dealSignal` as an optional estimate and use the public source URL to check any listing that matters for a decision. A missing deal signal means `enrichWithAI` was off, the AI call was unavailable, or the call limit was reached.

### Integrations and API

You can call the Actor from an Apify integration, a scheduled task, or your own program. The run's default dataset holds the listing records. Use an Apify token through your normal secret management process in API examples below. Replace the placeholder token with your own environment variable at execution time.

Python example:

```python
from apify_client import ApifyClient
import os

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("axiomworks/craigslist-listings-scraper").call(run_input={
    "cities": ["sfbay"], "category": "bia", "searchQuery": "road bike", "maxItems": 20
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["id"], item["title"], item["sourceUrl"])
```

JavaScript example:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('axiomworks/craigslist-listings-scraper').call({
  cities: ['austin'], category: 'cta', maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => process.stdout.write(`${item.id} ${item.title}\n`));
```

cURL example:

```bash
curl -X POST 'https://api.apify.com/v2/acts/axiomworks~craigslist-listings-scraper/run-sync-get-dataset-items' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"cities":["sfbay"],"category":"bia","searchQuery":"road bike","maxItems":20}'
```

The synchronous endpoint is useful for smaller runs. For larger searches, start a run and fetch its dataset once the run finishes. Stable IDs let you merge later runs into a table without creating a new row for every repeated listing. Retain the source URL with each row so a user can inspect the public page directly.

Search URLs preserve the case of their query text and override the category input for that target. A malformed URL fails input validation. A temporary source failure finishes with zero results and a clear status message. The Actor checks the remaining result budget before requesting a detail batch, reducing unused page requests when a run reaches its spending limit.

### FAQ

**Does this require a Craigslist account?** No. The search feed and optional listing pages used here are public.

**Can I search several cities in one run?** Yes. Pass an array of city hostnames. The Actor processes them in order and applies one shared `maxItems` limit.

**Will `maxItems: 7` return exactly seven records?** It returns seven when at least seven matching, unique listings exist. It never pushes more than seven. If fewer exist, the dataset contains only the listings found.

**Why is the description empty?** Descriptions are empty only when `includeDetails` is set to false. Listings whose page cannot be read are skipped.

**Why is a price empty?** Some Craigslist posts have no numeric asking price. The Actor preserves that absence rather than estimating a value.

**Does the AI signal prove a bargain or detect fraud?** No. It is a third-party AI judgment based on the fields available to the Actor. Verify important claims on the public listing and through your usual due diligence.

**Can the output include seller contact information?** No. This Actor only collects public search and listing page fields and does not enter reply flows.

**What happens when a city or search has no matches?** The Actor finishes with zero records and logs a warning. An unknown city is skipped, so other valid cities can still return results.

### Is it legal to scrape Craigslist?

Public page access and the legal rules for collecting or using data depend on your location, purpose, and the target site's terms. Craigslist's terms and robots guidance may restrict automated access. Review them and seek legal advice for your use case. Use the Actor at modest rates, avoid personal contact flows, and do not use the results to send unwanted messages. This Actor is not affiliated with, endorsed by, or sponsored by Craigslist. It is an independent tool that reads public pages only.

### Feedback

If a field stops filling, a city no longer resolves, or the search response changes, report the input you used, the approximate run time, and the affected field. The public search endpoint uses a compact internal format that can change. A reproducible report helps distinguish a source format change from a removed listing or a narrow query. Avoid including account credentials or private details in feedback.

# Actor input Schema

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

Required. Craigslist city hostnames or full search URLs, e.g. \["sfbay","austin"] or \["https://sfbay.craigslist.org/search/bia?query=road+bike"]. Hostname = the part before .craigslist.org. An unknown city or dead URL is skipped; if nothing is found the run still succeeds with 0 results and a status message naming what to change.

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

Craigslist category code, default "sss" (all for sale). Examples: "bia" bicycles by owner, "cta" cars and trucks, "apa" apartments, "jjj" jobs, "ela" electronics. A category inside a search URL overrides this. Unknown codes return 0 results with a message. Output categoryAbbr may differ (e.g. bia listings report "bik").

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

Words to find in listings, e.g. "road bike" or "iphone 14". Empty string (default) returns all listings in the category, newest first.

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

Optional extra keywords searched in each city in addition to searchQuery, e.g. \["gravel bike","fixie"]. Results are de-duplicated across queries and tagged in matchedQuery. Default none.

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

Maximum number of unique listings for the whole run (not per city), 1-10000, default 50. Use 5-20 for a quick test.

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

Optional. Only keep listings priced at or above this number in local currency, e.g. 100. Listings without a price are kept. Integer 0 or more.

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

Optional. Only keep listings priced at or below this number in local currency, e.g. 500. Listings without a price are kept. Integer 0 or more.

## `postedWithinHours` (type: `integer`):

Optional. Only keep listings posted in the last N hours, e.g. 24 for the last day. Integer 1 or more; paging stops once older listings appear.

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

Result order: newest (default), relevance, priceAsc (cheapest first) or priceDesc (most expensive first). With maxItems this gives the N cheapest or priciest listings per city. postedWithinHours only stops paging early with newest; listings without a price come first with priceAsc.

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

true (default) opens each listing page to add description and attributes; about 1 second per listing, unreadable pages are skipped. false returns search-feed fields only (no description/attributes) and is much faster; use it for large maxItems.

## `enrichWithAI` (type: `boolean`):

Optional, default false. true adds dealSignal ("underpriced", "fair", "overpriced" or "unknown") to each listing using a third-party AI processor; extra charge per enriched result. false leaves dealSignal null.

## `maxJevCalls` (type: `integer`):

Cap on AI calls per run, 0-500, default 500. Only used when enrichWithAI is true.

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

Apify Proxy settings, e.g. {"useApifyProxy": true}. Default is datacenter proxy; blocked requests retry on a fresh session and then on residential proxy.

## Actor input object example

```json
{
  "cities": [
    "sfbay"
  ],
  "category": "bia",
  "searchQuery": "road bike",
  "searchQueries": [],
  "maxItems": 20,
  "sortBy": "newest",
  "includeDetails": true,
  "enrichWithAI": false,
  "maxJevCalls": 500,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Collected Craigslist listing records.

# 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 = {
    "cities": [
        "sfbay"
    ],
    "category": "bia",
    "searchQuery": "road bike",
    "maxItems": 20,
    "includeDetails": true,
    "enrichWithAI": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("axiomworks/craigslist-listings-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 = {
    "cities": ["sfbay"],
    "category": "bia",
    "searchQuery": "road bike",
    "maxItems": 20,
    "includeDetails": True,
    "enrichWithAI": False,
}

# Run the Actor and wait for it to finish
run = client.actor("axiomworks/craigslist-listings-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 '{
  "cities": [
    "sfbay"
  ],
  "category": "bia",
  "searchQuery": "road bike",
  "maxItems": 20,
  "includeDetails": true,
  "enrichWithAI": false
}' |
apify call axiomworks/craigslist-listings-scraper --silent --output-dataset

```

## MCP server setup

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