# OfferUp Listings Scraper — Keyword, ZIP & Item URLs (`khadinakbar/offerup-listings-scraper`) Actor

Scrape public OfferUp US listings by keyword+ZIP, search URL, or item URL. Title, price, location, images; optional seller enrich. MCP-ready.

- **URL**: https://apify.com/khadinakbar/offerup-listings-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** E-commerce, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 listing founds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## OfferUp Listings Scraper — Keyword, ZIP & Item URLs

Use this Apify Actor to scrape **public OfferUp US marketplace listings** by keyword + ZIP, OfferUp search URL, or item URL/ID. Resellers, deal hunters, and lead-gen agents get a structured dataset of listing rows with title, price, location, images, and optional seller/description enrichment — cookieless, no login, MCP-ready.

Use this Actor when you need US local classified inventory from OfferUp into JSON. When the next workflow is Facebook Marketplace or another country, continue with a sibling marketplace Actor instead.

### Best fit for this Actor

- You need **public OfferUp US listings** near a ZIP (keyword search, search URL, or direct item URL/ID).
- You want flat fields (`title`, `price`, `locationName`, `url`, `imageUrl`) plus optional seller/description enrichment.
- You want one MCP-ready tool with terminal `OUTPUT.outcome` and Pay per event billing.
- For Facebook Marketplace, Craigslist-only workflows, or private OfferUp chat, route to a different tool.

### Use case: Seattle phone sourcing sheet

A reseller preparing a weekend buy list runs this Actor with `searchQueries: ["iphone"]`, `zipCode: "98101"`, and `maxItems: 30`. Each row has `title`, `price`, `locationName`, and `url`. They sort by price, open the best five OfferUp item pages, then turn on `fetchDetails` for a shortlist of three to pull description and public seller rating before driving out.

### Quick start input

```json
{
  "searchQueries": ["iphone"],
  "zipCode": "98101",
  "radiusMiles": 30,
  "maxItems": 3,
  "fetchDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "US"
  }
}
```

Also supports `searchUrls`, `listingUrls` / `listingIds`, price/condition filters, and sort. Prefill keeps quality tests at three listings.

### What data you receive

One dataset row per unique public listing.

```json
{
  "listingId": "3f39a8bc-cf5b-32ce-a775-a8998113db81",
  "url": "https://offerup.com/item/detail/3f39a8bc-cf5b-32ce-a775-a8998113db81",
  "title": "Example listing",
  "price": 200,
  "formattedPrice": "$200",
  "locationName": "Bremerton, WA",
  "imageUrl": "https://images.offerup.com/...",
  "flags": ["LOCAL_PICKUP"],
  "sourceQuery": "iphone",
  "sourceZip": "98101",
  "enriched": false,
  "scrapedAt": "2026-09-17T12:00:00.000Z"
}
```

| Field | Description |
|-------|-------------|
| title, price, condition | Listing card basics |
| locationName, url, imageUrl | Where and how to open it |
| flags | e.g. LOCAL\_PICKUP |
| description, photos, seller\* | When `fetchDetails` or direct listing URLs |
| categoryL1/L2, shipping flags | Enriched only |

### Pricing

Pay per event plus platform usage (`isPPEPlatformUsagePaidByUser: true`). Open the live Pricing tab in Apify Console for the current source of truth.

| Event | When | Price |
|---|---|---:|
| `apify-actor-start` | Every run | $0.00005 |
| `listing-found` | Each accepted search-card row | $0.005 |
| `listing-enriched` | Each accepted detail/enrich row | $0.012 |

A 3-listing search without enrich is about `$0.01505` in events plus Residential proxy/compute. Invalid input and valid-empty searches leave listing events unbilled.

### How it works

1. Primary path: OfferUp public GraphQL (`GetModularFeed` / listing detail). **Apify Residential US is required for OfferUp geo.**
2. If Cloudflare/geo blocks HTTP, the Actor attempts a Playwright intercept fallback when available.
3. Each accepted row is charged then pushed (`Actor.charge` → `pushData`).
4. Terminal `OUTPUT` / `RUN_SUMMARY` use COMPLETE, PARTIAL, VALID\_EMPTY, INVALID\_INPUT, UPSTREAM\_FAILED, or CONFIG\_ERROR.

### Honest contract and best results

- Public US listings only. No OfferUp account, DMs, or private seller contact data.
- Empty search results → `VALID_EMPTY` (succeeded, zero listing charges).
- Total block with zero rows → `UPSTREAM_FAILED` (honest upstream outage).
- Keep `maxItems` small while testing; raise it only after a ZIP+keyword returns useful cards.
- Prefer keyword + 5-digit ZIP; paste `searchUrls` when you already have OfferUp filters in the browser.
- Leave `fetchDetails` off for discovery; turn it on for shortlists that need description and public seller fields.
- Always pin `apifyProxyCountry: "US"` — non-US exits often return Geolocation Unavailable.

### Why agents choose this Actor

- One tool for keyword+ZIP, search URL, and item URL/ID.
- Predictable PPE with usage pass-through and a hard `maxItems` ceiling.
- Flat fields stay inside a small token budget for MCP clients.
- Terminal `OUTPUT.outcome` makes empty, upstream-outage, and invalid-input states machine-readable.

### Agent checklist

1. Supply `searchQueries` + `zipCode`, or OfferUp `searchUrls` / `listingUrls` / `listingIds`.
2. Cap `maxItems` to the budget you can spend at `$0.005` (or `$0.012` when enriched).
3. Keep Residential US in `proxyConfiguration`.
4. After the run, read `OUTPUT.outcome`, then the dataset.
5. Zero rows with `VALID_EMPTY` means the query matched nothing public — change keyword or ZIP.
6. When `OUTPUT.outcome` is `UPSTREAM_FAILED`, retry once with a fresh Residential session before escalating.

### Use through the API

```bash
curl "https://api.apify.com/v2/acts/khadinakbar~offerup-listings-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "searchQueries": ["iphone"],
    "zipCode": "98101",
    "maxItems": 3,
    "proxyConfiguration": {
      "useApifyProxy": true,
      "apifyProxyGroups": ["RESIDENTIAL"],
      "apifyProxyCountry": "US"
    }
  }'
```

When the Actor completes, read dataset items from the default dataset and the `OUTPUT` record from the default key-value store.

### Use with AI agents through Apify MCP

> Scrape OfferUp US listings for "iphone" near ZIP 98101. Return up to 10 rows with title, price, location, and URL. Keep Residential US. Stay on public listing fields only.

Point the MCP client at <https://mcp.apify.com>?tools=khadinakbar/offerup-listings-scraper, call `khadinakbar/offerup-listings-scraper`, then read the dataset and `OUTPUT.outcome`.

### Builder's note

I built this Actor on OfferUp's public GraphQL after I found that `GetModularFeed` over Apify Residential US returns real cards for ZIP-centered searches. In my testing, non-US exits surface Geolocation Unavailable, so the Actor defaults to Residential US and finishes `UPSTREAM_FAILED` when every route is unavailable rather than inventing listings. My goal is truthful billing: dataset views stay view-only so detail enrich paths keep optional seller fields without schema crashes.

### Connect the workflow

When the next market is US auction comps, continue with [eBay All-in-One Scraper](https://apify.com/khadinakbar/ebay-all-in-one-scraper).

For handmade listing research after OfferUp discovery, feed titles into [Etsy All-in-One Scraper](https://apify.com/khadinakbar/etsy-all-in-one-scraper).

When the SKU is a car and you need US vehicle listings after OfferUp discovery, start with [Autotrader Scraper](https://apify.com/khadinakbar/autotrader-scraper).

### FAQ and responsible use

**Is this affiliated with OfferUp?** OfferUp is a trademark of its owner. This independent Actor is not affiliated with, associated with, or endorsed by OfferUp, Inc.

**Is scraping allowed?** You are responsible for complying with OfferUp’s Terms of Service and applicable laws (including privacy rules). Use only for legitimate purposes on publicly available data.

**Why was my run empty?** A valid keyword with zero public matches finishes `SUCCEEDED` / `VALID_EMPTY`. Missing ZIP or empty input finishes `INVALID_INPUT`. When every search route is unavailable the outcome is `UPSTREAM_FAILED`.

**Does it return emails or phones?** No. Public listing and seller-display fields only.

Open the Actor **Issues** tab for bugs. This Actor is independent research tooling, not legal, investment, or sourcing advice.

# Changelog

This Actor's version history is a separate document: https://apify.com/khadinakbar/offerup-listings-scraper/changelog.md

# Actor input Schema

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

Keywords to search on OfferUp (e.g. iphone, sofa, bicycle). Requires zipCode. Each unique listing costs listing-found (or listing-enriched when fetchDetails is on).

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

Paste OfferUp search page URLs. ZIP and query are parsed from the URL when present (q=, ZIP=, DISTANCE=, cid=).

## `zipCode` (type: `string`):

5-digit US ZIP used as the search center for keyword searches. Required when using searchQueries without ZIP in searchUrls.

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

Search radius in miles from the ZIP (1–500). Default 30.

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

Maximum unique listings to return across all searches and URLs. Prefill 3 keeps quality tests cheap. Hard cap 1000.

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

Optional minimum listing price filter in USD.

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

Optional maximum listing price filter in USD.

## `conditions` (type: `array`):

Optional condition filters. Leave empty for any condition.

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

OfferUp sort order for search results.

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

Optional OfferUp category id (cid) such as 1.5 for Electronics. Leave blank for all categories.

## `listingUrls` (type: `array`):

Direct OfferUp item URLs (https://offerup.com/item/detail/{id}). Skips search and always returns detail fields when the listing exists.

## `listingIds` (type: `array`):

OfferUp listing UUIDs. Same as listingUrls without the full URL.

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

When true, enrich each search result with description, photos, seller profile, category, and shipping flags. Charges listing-enriched ($0.012) instead of listing-found ($0.005). Direct listingUrls always include details.

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

REQUIRED: Apify Residential proxies with country US. OfferUp geo-blocks non-US IPs.

## Actor input object example

```json
{
  "searchQueries": [
    "iphone",
    "sofa"
  ],
  "searchUrls": [
    "https://offerup.com/search?q=iphone&ZIP=98101"
  ],
  "zipCode": "98101",
  "radiusMiles": 30,
  "maxItems": 3,
  "sortBy": "bestMatch",
  "listingUrls": [
    "https://offerup.com/item/detail/3f39a8bc-cf5b-32ce-a775-a8998113db81"
  ],
  "fetchDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

Default Apify dataset containing one OfferUp listing per row (title, price, location, images; optional seller/description enrich).

## `summary` (type: `string`):

Machine-readable OUTPUT with outcome, itemsPushed, charges, and warnings.

## `runSummary` (type: `string`):

Detailed RUN\_SUMMARY record for diagnostics.

# 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 = {
    "searchQueries": [
        "iphone"
    ],
    "zipCode": "98101",
    "radiusMiles": 30,
    "maxItems": 3,
    "sortBy": "bestMatch",
    "fetchDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/offerup-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 = {
    "searchQueries": ["iphone"],
    "zipCode": "98101",
    "radiusMiles": 30,
    "maxItems": 3,
    "sortBy": "bestMatch",
    "fetchDetails": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/offerup-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 '{
  "searchQueries": [
    "iphone"
  ],
  "zipCode": "98101",
  "radiusMiles": 30,
  "maxItems": 3,
  "sortBy": "bestMatch",
  "fetchDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call khadinakbar/offerup-listings-scraper --silent --output-dataset

```

## MCP server setup

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