OfferUp Scraper - Listings, Prices, Sellers & Location Data
Pricing
from $1.00 / 1,000 listing results
OfferUp Scraper - Listings, Prices, Sellers & Location Data
Scrape OfferUp listings by keyword, location and radius. Returns 40+ fields per item: price, condition, GPS, full photo set, category tree and rich seller profiles (rating, items sold, join date, verification). Search and URL modes, 5 sort orders, price and condition filters.
Pricing
from $1.00 / 1,000 listing results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
5
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
OfferUp Scraper: Listings, Sellers, Prices & Category Data
OfferUp Scraper turns OfferUp, the US mobile-first local marketplace, into structured JSON. Search by keyword and location, or paste OfferUp search and item links, and get price, condition, geo coordinates, a full photo set, a three-level category tree and a complete seller profile per listing. Five native sort orders, price and condition filters, genuine price-drop detection, resumable runs and Incremental mode for recurring monitoring are all built in, ready to export to JSON, CSV or Excel, or pull through the API.
Why This Scraper?
- Two ways in. Keyword search with location, radius, price and condition filters, or paste OfferUp search and item URLs directly.
- 40+ fields when Fetch full details is on, including GPS coordinates, the full photo set, a three-level category tree and a complete seller profile (rating, items sold, join date, response time, verification).
- Five native sort orders, plus price range and multi-condition filtering, applied at the source rather than filtered after the fact.
- Genuine price-drop detection. Compares OfferUp's own recorded original price against the current price, not just a low asking price.
- Walks the whole catalogue by default. Max pages defaults to unlimited: the run walks every result page per query/URL until Max items, OfferUp's own last page, or a repeat-page guard stops it.
- Resumable, delta-friendly runs. Point Resume from a previous run at an earlier run or dataset to collect only what it doesn't already have, and Incremental mode tracks a recurring search itself and tags what changed.
- Checkpointed automatically. A platform migration or a manual restart of an interrupted run picks up where it left off instead of re-collecting and re-charging listings already pushed.
Use Cases
- Resale and arbitrage: monitor a category or keyword for genuine seller price drops to spot flip opportunities before they're gone.
- Local market research: track pricing and condition mix for a product category across cities, ZIP codes or radii.
- Recurring seller/category watch: schedule a search with Incremental mode on and review only new, updated or disappeared listings.
- Lead generation for local services: pull listings in categories like furniture, electronics or vehicles alongside seller responsiveness and verification signals.
- Marketplace data science: build a dataset of local pricing behavior over time using scheduled Incremental runs.
Data You Get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
listingId | 00000000-0000-0000-0000-000000000000 |
title | Sample Listing Title |
price | 799 |
priceText | "$799" |
isFirmPrice | false |
conditionText | Used |
locationName | Miami, FL |
url | https://offerup.com/item/detail/00000000-0000-0000-0000-000000000000 |
imageUrl | https://images.offerup.com/000000000-254x250.jpg |
flags | ["LOCAL_PICKUP"] |
description (when Fetch full details is on) | Full item description text. |
latitude / longitude (when enabled) | 25.0000 / -80.0000 |
distanceMiles (when enabled) | 4.9 |
categoryL1Name / categoryL2Name / categoryL3Name (when enabled) | Electronics & Media / Cell phones & Accessories / Smartphones |
photos (when enabled) | [{ "uuid": "0000", "url": "https://images.offerup.com/000-1440x1920.jpg" }] |
postDate (when enabled) | 2026-01-01T00:00:00.000Z |
originalPrice / isOnSpecial (when enabled) | 899 / true |
savingsAmount / discountPercent (when enabled) | 100 / 11 |
localPickupEnabled / shippingEnabled (when enabled) | true / false |
sellerName (when enabled) | Jane Seller |
sellerRating / sellerReviewCount (when enabled) | 5 / 89 |
sellerItemsSold / sellerDateJoined (when enabled) | 564 / 2016-01-01T00:00:00Z |
sellerResponseTime (when enabled) | Responds in a few minutes |
sellerIsTruYou (when enabled) | true |
The remaining fields cover secondary detail: conditionCode (numeric), quantity, photoCount, badges, discussionCount, isLocal, isMerchantItem, canShipToBuyer, shippingPrice, categoryL1Id/categoryL2Id/categoryL3Id, sellerId, sellerAvatar, sellerPrimaryBadge/sellerSecondaryBadge, sellerItemsPurchased, sellerPublicLocation, sellerIsAutosDealer, sellerIsBusinessAccount, sellerIsPremium, sellerWebsiteLink, and sellerPhone when OfferUp exposes it on the seller's profile. All of these "when enabled" fields need Fetch full details on (search mode) or come from the detail page automatically (a pasted item URL, or Price drops only). vehicleMiles (vehicle listings only) comes from the search-result card instead, so it's populated in search mode without needing Fetch full details, and is null on a pasted item URL. One more distinction worth knowing: a pasted single item URL always reads the detail page itself, so priceText, imageUrl, imageWidth, imageHeight and vehicleMiles come back null and flags comes back [], since those fields exist only on search-result cards, which a direct item-page fetch never produces.
How to Use
- Pick a mode:
search(keyword + location) orurl(paste OfferUp search or item links). - Fill in the fields for that mode, and switch on Fetch full details if you need description, GPS, category and seller data.
- Set Max items to control run size and cost, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Search a city with filters:
{"mode": "search","searchQueries": ["iphone 14"],"location": "Miami, FL","radiusMiles": 30,"priceMin": 200,"priceMax": 600,"condition": ["USED", "REFURBISHED"],"sortBy": "-price","maxItems": 50}
Paste OfferUp URLs (a search page and an item page), with full details:
{"mode": "url","urls": ["https://offerup.com/search?q=sofa&radius=20","https://offerup.com/item/detail/00000000-0000-0000-0000-000000000000"],"fetchDetails": true,"maxItems": 30}
Genuine price drops only (detail fetch is auto-enabled for this):
{"mode": "search","searchQueries": ["couch"],"location": "Miami, FL","priceDropOnly": true,"maxItems": 40}
Recurring monitoring of the same search (Incremental mode), including EXPIRED detection:
{"mode": "search","searchQueries": ["iphone 14"],"location": "Miami, FL","incrementalMode": true,"emitExpired": true,"maxItems": 0}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/offerup-scraper").call(run_input={"mode": "search","searchQueries": ["iphone 14"],"location": "Miami, FL",})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["price"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/offerup-scraper').call({mode: 'search',searchQueries: ['iphone 14'],location: 'Miami, FL',});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Max items is the real cap, Max pages is just a safety bound
maxItems is the single stopping point for a run (across every query/URL); set it to 0 for unlimited. maxPages (also 0 by default) is only an optional per-query ceiling on how many result pages get walked; it never raises the cap set by maxItems, it can only lower it. Leave maxPages at 0 to walk an entire query to its natural end.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted or earlier run: paste its run or dataset id and this run skips every listing it already holds, so you don't pay twice. Progress is also checkpointed automatically during a run, so a platform migration or a restart of an interrupted run resumes exactly where it stopped. - Incremental mode (
incrementalMode) is for scheduled runs over the same search. Each listing is classifiedNEW,UPDATED(withchangedFields), orUNCHANGED(suppressed and not billed unlessemitUnchangedis on) by default. A listing is only marked gone (and anEXPIREDrow emitted) whenemitExpiredis on and the run completed a full, uncapped scan of the tracked search, never on a Resume, a Max items cap, or a run stopped by an error;REAPPEAREDtherefore requires a prior run like that, and never happens withemitExpiredoff.stateKeynames or shares the stored baseline; leave it empty to auto-derive one from your search/URLs and filters. Five fields (discussionCount,sellerLastActive,sellerResponseTime,distanceMiles,isLocal) are excluded from change detection because they drift on their own between runs, independent of the listing itself; their real current values are still returned in every row. With Incremental mode off, output is exactly as before.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each listing, not the full JSON. Each item becomes one entry with a title and its key fields (price, condition, location, category, seller, URL) flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (keyword + location) or url (paste OfferUp URLs); filter fields below are ignored in url mode. |
searchQueries | array | ["iphone"] | Keywords to search (search mode). Each query is scraped independently. |
location | string | no default (prefill: Miami, FL) | City ('Miami, FL'), ZIP ('33101'), or raw 'lat,lon'. Empty searches near the connection's own location. |
radiusMiles | integer | 50 | Radius in miles around the location (1 to 500). |
priceMin | integer | no default | Only return listings priced at or above this amount (USD). |
priceMax | integer | no default | Only return listings priced at or below this amount (USD). |
condition | array | no default | Filter by condition: NEW, OPEN_BOX, REFURBISHED, USED, BROKEN, OTHER. Leave empty for any condition. |
priceDropOnly | boolean | false | Keep only listings with a genuine seller price drop (search mode). Auto-enables fetchDetails (and its per-item charge) even if left off, since price-drop data only lives on the detail page. |
sortBy | string | best_match | best_match, -posted (newest), distance (closest), price (low to high), -price (high to low). |
urls | array | no default (prefill: sample search URL) | OfferUp search or item detail URLs (url mode). Search URLs page forward automatically. |
fetchDetails | boolean | false | Visit each listing page for description, GPS, full photo set, category tree, fulfillment details and the complete seller profile. Adds a per-item charge. Always on when priceDropOnly is set. |
maxItems | integer | 20 | Stop after this many listings total (across all queries/URLs). 0 = unlimited. |
maxPages | integer | 0 | Optional safety cap on result pages walked per query/URL. 0 walks the whole catalogue until maxItems, the site's own last page, or a repeat-page guard stops it. |
resumeFromRunId | string | no default | ID of a previous run (or dataset) of this actor. Listings already in that dataset are skipped, so this run returns only new ones. |
incrementalMode | boolean | false | For a search you run on a schedule: tracks this search's own listings between runs and tags each row's changeType as NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED. |
stateKey | string | no default | Optional label naming the tracked Incremental mode baseline. Leave empty to auto-derive one from your search/URLs and filters. |
emitUnchanged | boolean | false | Incremental mode: also return and bill listings with no change since the last run. |
emitExpired | boolean | false | Incremental mode: also return and bill a row for each previously tracked listing no longer found, only once a run completes a full scan (never on Resume, a Max items cap, or an error-stopped scan). |
proxy | object | Residential US | Connection settings. OfferUp geo-blocks non-US and datacenter connections; Residential proxy with country US is required for reliable results. |
mcpConnectors | array | no default | Optional: send a summary of each listing to apps you authorized under Integrations. |
notionParentPageUrl | string | no default | Notion connector only: page under which item pages are created. |
maxNotifyListings | integer | 50 | Cap on items exported to each connector per run. Does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live listing.
{"listingId": "00000000-0000-0000-0000-000000000000","url": "https://offerup.com/item/detail/00000000-0000-0000-0000-000000000000","title": "Sample Listing Title","price": 799,"priceText": "$799","isFirmPrice": false,"conditionText": "Used","conditionCode": 40,"locationName": "Miami, FL","latitude": 25.0000,"longitude": -80.0000,"distanceMiles": 4.9,"flags": ["LOCAL_PICKUP"],"categoryL1Name": "Electronics & Media","categoryL2Name": "Cell phones & Accessories","categoryL3Name": "Smartphones","description": "Full item description text.","originalPrice": 899,"isOnSpecial": true,"savingsAmount": 100,"discountPercent": 11,"photos": [{ "uuid": "0000", "url": "https://images.offerup.com/000-1440x1920.jpg", "width": 1440, "height": 1920 }],"photoCount": 1,"postDate": "2026-01-01T00:00:00.000Z","localPickupEnabled": true,"shippingEnabled": false,"sellerName": "Jane Seller","sellerRating": 5,"sellerReviewCount": 89,"sellerItemsSold": 564,"sellerDateJoined": "2016-01-01T00:00:00Z","sellerResponseTime": "Responds in a few minutes","sellerIsTruYou": true}
With incrementalMode on, every row also carries changeType (NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED), changedFields, firstSeenAt and lastSeenAt.
Plan Requirement
OfferUp only serves US connections and geo-blocks datacenter and non-US traffic. Use Apify Residential proxy with country US, which is the default proxy configuration for this actor, for reliable results.
FAQ
How much does it cost?
You pay per listing returned, plus a small per-item surcharge when Fetch full details (or Price drops only, which turns it on automatically) is active. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.
Is it legal to scrape OfferUp?
This actor collects only publicly available marketplace listings. You are responsible for how you use the data: follow OfferUp's terms and the laws that apply to you, get legal advice before any commercial use, and never use seller names, locations or contact details in ways a seller didn't invite (for example unsolicited outreach).
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only NEW and UPDATED rows for that search by default; unchanged listings are skipped unless Emit unchanged listings is on, and turning on Emit expired listings also gets you EXPIRED rows once a run completes a full scan, plus REAPPEARED rows on a later run for a listing that was marked gone that way.
Why does pasting one item URL cost more than a plain search listing?
A pasted item URL can only be read from its own detail page, so that fetch always happens and is always billed the detail-enrichment surcharge, even though Fetch full details defaults to off for search mode. A search-mode listing only gets that fetch (and charge) when Fetch full details or Price drops only is turned on. A pasted item URL record also never carries priceText, imageUrl, the image dimension fields or vehicleMiles, and its flags comes back empty, because those only ever come from a search-result card, not the detail page.
Why did my run return an empty dataset instead of failing?
OfferUp only serves US residential connections; a datacenter or non-US connection is geo-blocked and returns 0 listings with a clear log warning and status message, so you can fix the connection and rerun rather than parse a generic error. The run does fail outright, with a clear message, when Resume from a previous run points at an id that can't be read, or when Incremental mode already has saved state for a search and you also set Resume from a previous run against it.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push listing summaries into Notion, Linear or Airtable.
🔗 Want more e-commerce data?
Pair this actor with these related scrapers from the same team:
| 🛒 Depop Scraper From $1/1K. Scrape Depop search results, listings, shops, and reviews to JSON, CSV, or... | 💼 Jobsite UK From $1/1K. Collect UK job listings from Jobsite.co.uk at scale. Search by keyword... |
| 💼 Reed.co.uk Scraper Scrape Reed.co.uk jobs, companies, courses, and reviews. Search by keyword, location... | 🏷️ ThredUp Scraper Scrape ThredUp resale listings by keyword, department, brand, size, condition, price or... |
| 🏠 Auction.com Scraper From $1/1K. Extract foreclosure, REO, short-sale, and auction listings from Auction.com... | 🛒 Mercari Japan Scraper From $0.80/1K. Scrape Mercari Japan listings, sellers and reviews at scale. Extract... |
💬 Support & custom scrapers
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.