# OfferUp API scraper — search the US local classifieds marketplace by keyword, category and location (ZIP or coordinates) and read the full listing record: price, condition, photos, category, pickup/shipping and the seller's public profile. Prices in USD. No account, no browser.

> Search OfferUp listings near a US location by keyword, by category, or both. Returns one row per listing with its id, title, numeric price, town, photo and OfferUp's own tile flags — about 44-50 rows per call — plus a `next_cursor` for the next page. The condition and whether the price is firm are NOT on a feed tile (measured 0 of 704 rows); `product/detail` fills both. Location is what scopes the query: give `zip_code` (resolved by OfferUp's own geocoder) or `latitude`+`longitude`. Use the returned `listing_id` with `product/detail` for the description, the photo gallery and the seller.
> ReefAPI engine `offerup` · 2 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/offerup/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed or blocked calls are free.
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http, `Authorization: Bearer <key>`) and your assistant can call these actions directly.

## Endpoints

### POST /offerup/v1/search — 1 credit
Search OfferUp listings near a US location by keyword, by category, or both. Returns one row per listing with its id, title, numeric price, town, photo and OfferUp's own tile flags — about 44-50 rows per call — plus a `next_cursor` for the next page. The condition and whether the price is firm are NOT on a feed tile (measured 0 of 704 rows); `product/detail` fills both. Location is what scopes the query: give `zip_code` (resolved by OfferUp's own geocoder) or `latitude`+`longitude`. Use the returned `listing_id` with `product/detail` for the description, the photo gallery and the seller.

**Parameters:**
- `query` (string, optional) — What to look for, in English. Optional — omit it and pass `category` instead to browse a whole category near the location. When BOTH are given, the category wins where the keyword has no local supply (measured: q=iphone + category=5 returns cars), so use one or the other unless you want that.
- `category` (string, optional) — OfferUp category id. Top level: 1=Electronics & Media, 2=Home & Garden, 3=Clothing, Shoes, & Accessories, 4=Baby & Kids, 5=Vehicles, 6=Toys, Games, & Hobbies, 7=Sports & Outdoors, 8=Collectibles & Art, 9=Pet supplies, 10=Health & Beauty, 11=Wedding, 12=Business equipment, 13=Tickets, 14=General. Sub-categories are dotted and any depth works — 2.1 Furniture, 1.2 Cell phones & Accessories, 5.1 Cars & Trucks. Every result of `product/detail` returns the listing's own `category.id`, which is how you discover the deeper ids.
- `zip_code` (string, optional) — US 5-digit ZIP the search is centred on. Resolved to coordinates by OfferUp's own geocoder (one extra ~0.7 s call), and the resolved city/state/lat/lon come back in `location`. Give this OR latitude+longitude. If you give neither, OfferUp falls back to the geographic centre of the US (Pretty Prairie, Kansas) and the results are Kansas listings — `location.source` says which happened.
- `latitude` (number, optional) — Latitude the search is centred on. Use with `longitude`; skips the ZIP lookup.
- `longitude` (number, optional) — Longitude the search is centred on. Use with `latitude`.
- `radius` (enum, optional, default "30") — Search radius in miles around the location. Only the five values OfferUp's own distance slider offers are accepted; anything else is rejected rather than silently ignored. 50 is the site's maximum — there is no nationwide search. [one of: 5, 10, 20, 30, 50]
- `price_min` (number, optional) — Lowest price in USD. Bites: min=100 & max=200 returned rows priced 100-200 only.
- `price_max` (number, optional) — Highest price in USD (OfferUp's own maximum is 1000000).
- `sort` (enum, optional, default "best_match") — Result order. These are the five orderings OfferUp's own sort drawer offers and each was verified to change the result set; anything else is rejected. [one of: best_match, newest, closest, price_asc, price_desc]
- `cursor` (string, optional) — Next page. Pass back the `next_cursor` from the previous response — verified to return a page with zero ids in common with the one before it. There is no page NUMBER: OfferUp pages this feed by cursor only, and it publishes no total, so `total_results` is null rather than a guess.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry on a fresh exit (1-6, default 3 — leave unchanged for typical use).

**Returns:** results[]{position, listing_id, title, price (number; null when call_for_price is true), price_display (OfferUp's own formatted string), currency=USD, call_for_price (true on a dealer listing whose price OfferUp hides on the tile — product/detail returns the number), is_firm_price (null on a search row — OfferUp does not fill it here), condition (null on a search row; product/detail has it), location{name, latitude (null on a search row), longitude (null)}, image{url,width,height}, vehicle_miles, flags[] (verbatim: LOCAL_PICKUP, CALL_FOR_PRICE), url, seller (null — OfferUp publishes no owner on a feed tile), rating (null — OfferUp has no product reviews), review_count (null), rating_scale (null)}, count, next_cursor, total_results (null — OfferUp publishes no total for this feed), location{latitude, longitude, zip_code, city, state, radius_miles, source ('zip_code' | 'coordinates' | 'offerup_default_us_centre')}, query, category, sort, ad_tiles_dropped, duplicate_rows_dropped (meta.pagination carries next_cursor + has_more)

**Example request body:**
```json
{
  "query": "couch",
  "latitude": 34.0522,
  "longitude": -118.2437
}
```

### POST /offerup/v1/product/detail — 1 credit
The full OfferUp listing record by `listing_id` (or listing URL): title, the seller's own description, numeric price and whether it is firm, condition, posted date, the complete photo gallery at full size, the category with its whole path, the listing's coordinates and town, pickup/shipping terms, vehicle attributes when it is a vehicle, and the seller's PUBLIC profile — display name, how many items they have sold and bought, when they joined, their stated response time and their verification badges.

**Parameters:**
- `listing_id` (string, required) — OfferUp listing id — the uuid in the listing URL (https://offerup.com/item/detail/ca6fcc12-… → ca6fcc12-…). A full listing URL is also accepted. Every `search` row returns it as `listing_id`.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry on a fresh exit (1-6, default 3 — leave unchanged for typical use).

**Returns:** listing{listing_id, internal_id, title, original_title, description, price (number), price_display, original_price, currency=USD, is_firm_price, quantity, sku, condition ('NEW' …), condition_code (OfferUp's own 0-100 integer, passed through untranslated), state, is_removed, is_local, is_merchant_item, posted_at, updated_at, location{name, latitude, longitude}, category{id, name, l1_id, l1_name, l2_id, l2_name, l3_id, l3_name}, images[]{id,url,width,height}, image, shipping{local_pickup, shipping_available, can_ship_to_buyer, buy_it_now, seller_pays_shipping, shipping_price, shipping_price_display}, seller{id, name, url, rating (null — OfferUp publishes no seller rating on this surface), review_count (null), rating_scale (null), items_sold, items_purchased, response_time, joined_at, location_name, is_business, is_premium, is_auto_dealer, is_verified, is_inactive, badges[] (labels only — no phone number or address), avatar}, vehicle{make, model, miles, color, body, fuel_type, transmission, title_status, mpg_city, mpg_highway, engine_cylinders, drive_train} (null when the listing is not a vehicle), rating (null — OfferUp has no product reviews), review_count (null), rating_scale (null), url}

**Example request body:**
```json
{
  "listing_id": "617edbe7-77f8-3284-a7da-d33032d49da0"
}
```

## More
- Try it live, no code: https://reefapi.com/playground?engine=offerup
- Human docs page: https://reefapi.com/docs/offerup
- Overview page: https://reefapi.com/offerup-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
