Quick answer
If you're building an OfferUp scraper and passing a zip_code query parameter expecting it to move the search location, stop — it does nothing. We confirmed this live, three separate times, before believing it. The parameter that actually pins a search to a location is a pair of coordinates carried in a session cookie, not a query string at all. Get this wrong and every "ZIP-targeted" search you run quietly falls back to wherever your proxy's exit IP happens to be that day.
Does OfferUp's zipCode parameter actually pin the search location? 📍
No. That's the finding that reshaped how the OfferUp Listings Scraper targets a location, and it cost us three live confirmations to accept it, because it's the kind of bug that looks like it's working. Pass a ZIP in an obvious-looking query param, get back a search results page, see plausible-looking listings — nothing about the response tells you the location didn't take.
The actual signal OfferUp reads is latitude/longitude, delivered as a session cookie named ou.location, set before the search request goes out:
LOCATION_COOKIE_NAME = "ou.location"
LOCATION_COOKIE_DOMAIN = "offerup.com"
# zipCode alone does NOT pin the location — confirmed live 3x.
# latitude/longitude are the actual pinning signal; resolve_zip_to_latlon
# geocodes the user's ZIP via zippopotam.us before the cookie is built.
So a zipCode input has to travel through an extra hop before it can do anything: geocode it to coordinates first (we use the free zippopotam.us API), then bake those coordinates into the cookie, then attach that cookie to the session that makes the search request. zipCode itself never reaches OfferUp's servers as a filter — it's purely a convenience input on our side that gets translated into the signal that actually works.
What happens if the exit IP isn't American? 🌐
A distinct, separate failure: 403 "Geolocation Unavailable". This is worth calling out on its own because it's easy to misdiagnose as a bot block and go reaching for browser fingerprint fixes that won't help.
It isn't a fingerprint problem — it's a geo-gate. OfferUp is a US-only marketplace, and a non-US exit IP gets refused before any bot-detection question is even asked. The fix lives entirely on the proxy side: pin the proxy pool to a US exit and confirm it, rather than debugging TLS impersonation profiles that were never the cause.
Two failure modes, two different symptoms, two different fixes — and conflating them means chasing the wrong one:
| Symptom | Actual cause | Fix |
|---|---|---|
| Listings come back, but from the wrong city |
zipCode param sent with no coordinate-derived cookie |
Geocode ZIP → lat/lon → ou.location cookie |
403 Geolocation Unavailable |
Non-US proxy exit IP | Pin proxy to a US exit |
Where does the listing data actually come from? 🧊
OfferUp server-renders both its search-results page and its listing-detail page into a <script id="__NEXT_DATA__"> JSON blob — standard Next.js SSR. The detail page's payload specifically is a normalized Apollo Client InMemoryCache: a flat dictionary keyed f"{__typename}:{opaqueId}", with every cross-reference between objects expressed as a pointer rather than an inline value:
{ "__ref": "Listing:4701d7d2-51cd-3f10-bc23-675fb1d1a32b" }
Reading that cache correctly means resolving each __ref pointer back to its real object before you can read a single field off it — a listing's seller isn't inlined next to the listing, it's a reference you follow into a separate cache entry. Apollo's InMemoryCache only normalizes objects that carry their own id; anything without one stays inlined instead. A parser that assumes one shape or the other will work on some listings and silently return partial rows on others, which is worse than failing outright because nothing tells you which listings it happened to.
The part that generalizes 🧭
A query parameter that looks like it should work is not evidence that it does. The only way we caught this was checking actual search results against actual known coordinates, three times, because the first two attempts produced plausible-but-wrong output rather than an error. When a target's filtering behavior can't be verified by staring at the response shape, verify it against ground truth instead.
A geo-gate and a bot-fingerprint block wear the same 403. They need opposite fixes — proxy geography for one, browser impersonation for the other — and treating them as the same problem burns time solving the wrong half.
What the Actor gives you
One typed row per matching listing, from a keyword search:
- title, price, condition, location, and primary photo from the fast search-tile pass
- optional deeper pass per listing: full description, category, seller name/rating/items-sold, and precise coordinates
- real ZIP-code location pinning via the coordinate-derived cookie, not a guessed query param
- price and radius filters matching OfferUp's own confirmed slider values
- Pydantic-validated rows, deduped, with stable listing IDs and ISO-8601 timestamps
The honest limitations 🚧
Read-only, public-listing data — no posting, messaging, or authenticated-account actions of any kind. Seller detail is limited to what OfferUp's own listing page exposes (name, rating, items sold); full seller profile pages are out of scope. Without a zipCode, search location follows the proxy's exit-IP geolocation, which can drift between runs.
FAQ
Do I need to pass a ZIP code?
No — omit it and the search centers on your proxy's exit-IP location. Set it for a precise, repeatable location regardless of which exit IP a given run happens to use.
Why did my ZIP-targeted search return listings from a different city?
Almost certainly a zipCode that never got converted to the coordinate cookie — the query-param path alone does not pin location on OfferUp.
I'm getting a 403 with "Geolocation Unavailable" — is my scraper blocked?
No, that's a geo-gate, not a bot block. Confirm your proxy is exiting from a US IP; OfferUp is US-only and refuses non-US traffic outright.
What's the difference between the fast pass and scrapeDetails=true?
The fast pass is one request per search query and returns title/price/condition/location/photo. scrapeDetails=true adds one request per listing for description, category, seller rating, and precise coordinates.
Pricing
$0.20 per run plus $0.0018 per unique result row — about $2.00 per 1,000 results. No subscription, no minimum, no card to start.
→ OfferUp Listings Scraper on Apify
Built by Devil Scrapes. We handle the location cookies, the geo-gates, and the normalized Apollo cache, so you get a flat table instead of a weekend.
Top comments (0)