# Craigslist Scraper - $0.85 per 1,000, No API Key (`dami_studio/craigslist-scraper`) Actor

A flat $0.85 per 1,000 listings plus $0.002 per run. Search any Craigslist city and category and get listing id, title, price, neighborhood, posted date, images and the listing URL. No account, no API key, no captcha wrangling. Blocked or empty runs are never charged.

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

## Pricing

Pay per event

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

## Craigslist Scraper

Give it a city and a search term. Get back structured Craigslist listings — id, title, price, location, neighborhood, posted date, category, image URLs and the listing URL. Turn on **Fetch full posting text** and each row also carries the seller's body text and the attribute table.

No API key. No login. No browser.

### Why this one

Craigslist's search pages are an empty shell that fills itself in from Craigslist's own JSON API. Most scrapers drive a headless browser at the shell and pay for the compute. This one talks to the JSON API directly with a real browser TLS fingerprint, so it is plain HTTP the whole way.

That has two effects you can measure:

- **10,000 listings come back in one request.** Roughly 7 seconds, 3.4 MB. There is no page-by-page crawl to wait on.
- **No browser means no bandwidth bill.** You pay $0.85 per 1,000 listings and nothing else — there is no compute or proxy charge stacked on top.

### Input

| Field | What it does |
|---|---|
| `city` | Craigslist subdomain — `newyork`, `sfbay`, `losangeles`, `london`. Plain names like `new york city` also resolve. Pass several to search several cities in one run. 707 Craigslist sites across 79 countries are recognised. |
| `query` | Search terms. Leave empty to take the whole category. |
| `category` | Category abbreviation. `sss` all for sale, `hhh` housing, `jjj` jobs, `ggg` gigs, `bbb` services, `ccc` community, `cta` cars & trucks, `eee` events. Leaf codes work too — `bia` bicycles, `apa` apartments, `fuo` furniture, `zip` free stuff. |
| `searchUrls` | Or skip the fields above and paste Craigslist search URLs. Filters already in the URL are kept. |
| `maxItems` | Total listings across all searches. Default 100, ceiling 50,000. |
| `fetchDetails` | Open every posting page for body text, attributes, street address and last-updated time. |
| `sort` | `newest` (default) or `relevant`. |
| `minPrice` / `maxPrice` | Price bounds. |
| `postalCode` + `radiusMiles` | Search around a ZIP instead of the whole city. |
| `sellerType` | `owner`, `dealer` or anyone. |
| `hasImage`, `titleOnly`, `postedToday` | The same switches Craigslist's own filter bar has. |

```json
{
  "city": ["sfbay"],
  "query": "toyota tacoma",
  "category": "cta",
  "maxItems": 500,
  "maxPrice": 25000,
  "fetchDetails": true
}
```

### Output

Every row:

`listingId`, `title`, `price`, `priceText`, `currency`, `url`, `postedAt`, `categoryId`, `categoryAbbr`, `categoryName`, `location`, `neighborhood`, `subarea`, `city`, `cityName`, `region`, `country`, `latitude`, `longitude`, `images`, `imageCount`, `searchUrl`, `scrapedAt`

With `fetchDetails` on, five more: `description`, `attributes`, `attributeTags`, `updatedAt`, `address`.

```json
{
  "listingId": "7951229837",
  "title": "21 Speed TREK NAVIGATOR 2.0 Hybrid Bicycle 15 inch frame",
  "price": 220,
  "priceText": "$220",
  "currency": "USD",
  "url": "https://www.craigslist.org/view/d/commack-21-speed-trek-navigator-20/pvSPx2jAvzkhXUUURwoxxd",
  "postedAt": "2026-08-07T18:12:58.000Z",
  "categoryId": 68,
  "categoryAbbr": "bik",
  "categoryName": "bicycles - by owner",
  "location": "Commack/Western Suffolk",
  "neighborhood": null,
  "subarea": "que",
  "city": "newyork",
  "cityName": "new york city",
  "region": "NY",
  "country": "US",
  "latitude": 40.843,
  "longitude": -73.2799,
  "images": ["https://images.craigslist.org/00m0m_kE7opkfWlRt_0jm0ew_600x450.jpg"],
  "imageCount": 3,
  "searchUrl": "https://newyork.craigslist.org/search/bia?query=trek",
  "scrapedAt": "2026-08-07T18:45:21.751Z",
  "description": "Selling 21 Speed TREK NAVIGATOR 2.0 Hybrid Bicycle. 26 inch tires, 15 inch frame...",
  "attributes": { "bicycle type": "hybrid/comfort", "frame size": "15 inches", "wheel size": "26 in" },
  "attributeTags": [],
  "updatedAt": "2026-08-07T18:07:58.000Z",
  "address": null
}
```

`attributes` is whatever Craigslist labelled for that category — `condition`, `odometer`, `VIN`, `transmission`, `title status` on cars; `rent period`, `pets cat`, `pets dog` on housing; `bicycle type`, `frame size` on bikes. `attributeTags` holds the unlabelled chips: `1BR / 1Ba`, `547ft2`, `available oct 1`.

### Pricing

**$0.85 per 1,000 listings** ($0.00085 each), plus $0.002 each time a run starts. Flat rate — no
volume tiers, no plan gates — and you are charged only for listings actually returned.

| Listings in the run | Listing fees | All-in, with the start fee |
|---|---|---|
| 100 | $0.085 | $0.087 |
| 1,000 | $0.85 | $0.852 |
| 10,000 | $8.50 | $8.502 |
| 100,000 | $85.00 | $85.002 |

Because the start fee is charged once per run rather than per city or per search, batching several
cities into one run is cheaper than running them separately. A run that returns no listings costs
only the $0.002 start fee. Blocks, bad input and empty searches write a labelled diagnostic row
instead of a listing, and diagnostic rows are never charged. `fetchDetails` adds `description`,
`attributes`, `attributeTags`, `updatedAt` and `address` to the same row at no extra charge — there
is no second event for the detail fetch.

### Limits

Read this part before you build on it.

- **Relevance sorting stops at 360 results.** That is Craigslist's cap, not ours. `sort: "newest"` has no such limit and is the default.
- **Craigslist does not sort by price.** Its API dropped the option. Sort the dataset yourself.
- **`neighborhood` is usually null.** Craigslist only assigns neighborhoods inside a handful of dense metros — about 7% of New York rows have one. `location` is populated on effectively every row.
- **One city per search.** Craigslist has no nationwide index. Pass a list of cities to cover more ground; each is a separate search.
- **`price` is null on postings with no price** — jobs, gigs, most community posts, and for-sale posts where the seller left it blank.
- **Cross-posts come back as separate rows.** A seller who posts the same couch to three subareas creates three postings with three ids, and Craigslist reports them separately. `listingId` is deduplicated within a run; identical text under different ids is not.
- **`fetchDetails` costs about one extra second per 6 listings.** Six detail pages run at a time. On 10,000 rows that is a long run — set `maxItems` deliberately.
- **Expired postings.** A posting deleted between the search and the detail fetch returns `detailError` on that row rather than failing the run.
- **The RSS feed is dead.** Craigslist answers `?format=rss` with HTTP 403 from every address. If you have old tooling built on it, that is why it stopped.

### FAQ

**Do I need a Craigslist account or API key?**
No. Everything it reads is public.

**Can it scrape all of Craigslist at once?**
No. Craigslist is organised as ~707 independent city sites with no combined index. You give it cities; it searches each one.

**How many listings can I get from one city?**
One request returns up to 10,000. Past that the scraper walks backwards through posting dates, so a large `maxItems` keeps going until the search is exhausted. New York's "all for sale" section had 118,618 live postings when this was written.

**Does it get the seller's phone number or email?**
No. Craigslist hides contact details behind a reply relay that requires solving a challenge. This scraper returns what the public posting shows. If a seller typed their number into the description, it will be in `description` — that is the seller's own doing, not an extraction.

**Does it work outside the US?**
Yes. 79 countries. Non-US sites are much thinner than US ones — London's for-sale section had 2 matches for "bike" while New York had 3,711.

**What do I get without `fetchDetails`?**
Everything except `description`, `attributes`, `attributeTags`, `updatedAt` and `address`. Titles, prices, images, coordinates, dates and URLs all come from the search response, so the cheap mode is not a stub.

**Why is `imageCount` sometimes higher than what I see on the page?**
It is the count Craigslist reports for the gallery. Sellers occasionally upload duplicates.

**Is this affiliated with Craigslist?**
No. It reads public pages. Check Craigslist's terms and your own local rules before you use the output commercially.

### Notes

Built as plain HTTP with `impit` for a Chrome TLS fingerprint. No Playwright, no residential proxy, no per-GB bandwidth bill.

# Actor input Schema

## `city` (type: `array`):

Craigslist city (the subdomain), e.g. "newyork", "sfbay", "losangeles", "london". Plain city names like "new york city" also work. Add several to search several cities in one run. 707 Craigslist sites in 79 countries are recognised.

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

What to search for, e.g. "mountain bike". Leave empty to return everything in the category.

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

Craigslist category abbreviation. Groups: sss = all for sale, hhh = housing, jjj = jobs, ggg = gigs, bbb = services, ccc = community, cta = cars & trucks, eee = events. Leaf categories work too: bia bicycles, apa apartments, fuo furniture, zip free stuff, ela electronics.

## `searchUrls` (type: `array`):

Optional. Paste full Craigslist search URLs instead of filling in the fields above, e.g. https://newyork.craigslist.org/search/bia?query=trek. Filters already in the URL are kept.

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

How many listings to return in total across all searches (1-50000). Each returned listing is one charged result.

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

Open each posting page to add the seller's body text, the attribute table (condition, make, bedrooms, ...), the street address when shown, and the last-updated time. Slower: roughly one extra request per listing.

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

Newest first is the only order that can return more than 360 listings - Craigslist caps relevance-ordered results at 360. Price sorting is not offered by Craigslist's API.

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

Optional lower price bound.

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

Optional upper price bound.

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

Optional. Search around this postal code instead of the whole city. Use together with the radius below.

## `radiusMiles` (type: `integer`):

Optional. Only used when a postal code is set.

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

Restrict to owner or dealer postings. Only meaningful in categories that have both.

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

Skip postings that have no images.

## `titleOnly` (type: `boolean`):

Match the search terms against listing titles instead of the whole posting.

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

Restrict to postings from today.

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

Optional. Leave as-is unless you need a specific egress. Residential proxy is not required.

## Actor input object example

```json
{
  "city": [
    "newyork"
  ],
  "query": "bike",
  "category": "sss",
  "searchUrls": [],
  "maxItems": 100,
  "fetchDetails": false,
  "sort": "newest",
  "sellerType": "any",
  "hasImage": false,
  "titleOnly": false,
  "postedToday": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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 = {
    "city": [
        "newyork"
    ],
    "query": "bike",
    "category": "sss",
    "searchUrls": [],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/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 = {
    "city": ["newyork"],
    "query": "bike",
    "category": "sss",
    "searchUrls": [],
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/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 '{
  "city": [
    "newyork"
  ],
  "query": "bike",
  "category": "sss",
  "searchUrls": [],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/craigslist-scraper --silent --output-dataset

```

## MCP server setup

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