Skip to content
Scrappa Get API key
OfferUp API 1 credit/request

OfferUp Search API Documentation

GET https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted

Scrappa's GET /api/offerup/search endpoint returns OfferUp second-hand listings as structured JSON, so you can build marketplace search, price monitoring, and lead generation without maintaining a scraper.

Searching OfferUp listings

A location is required: pass zipcode, or both lat and lon. Without one, OfferUp answers with listings geolocated to the proxy server's own area rather than yours, which makes results vary between calls. Use Geocode to turn a zip code or place name into coordinates first.

Filters cover q for keywords, cid for category browsing, price_min and price_max, radius in miles, condition, and sort. Invalid enum values are rejected with a 422 rather than silently ignored, because OfferUp returns unfiltered results instead of an error.

There are two category parameters and they are not interchangeable. cid browses the taxonomy on its own; category_id narrows a keyword search and therefore requires q. Sending cid together with category_id is rejected with a 422 rather than letting one silently outrank the other, because which filter OfferUp honours in that case was never something we measured.

What search returns, and what it does not

Each result carries a listing id, title, price, location name, image URL and vehicle mileage where applicable. Ad placements are removed, and repeated listings are deduplicated within the page.

Four fields are detail-only on OfferUp's search resolver and come back as null with hydrated: false: condition, post_date, category, and fulfillment_details. Hydrating a full page would mean dozens of extra upstream requests behind a single charge, so this endpoint stays at one lookup per call. Use Item Details to fill those fields in.

There is no limit parameter and no result total. OfferUp controls page size and never reports a total count, so total_results is always null — derive completeness from the listings themselves.

Delivery filtering is not available

OfferUp's search resolver does not support filtering by delivery or shipping method. Rather than accept a parameter that quietly returns everything, this endpoint omits it. Read fulfillment_details from Item Details to tell local pickup from shipping on a listing you have already selected.

When OfferUp is throttling us

If the upstream rate limits the proxy pool, every endpoint here answers with a 503 and a JSON error object instead of a Retry-After 429. The Retry-After response header appears only when OfferUp itself sent one. When it did not, the body carries suggested_retry_after — our own measured backoff, offered as guidance rather than as an upstream instruction. Neither shape is billed.

Paginating without an end signal

OfferUp repeats listings across pages and does not signal the last page. Pass the returned page_cursor to continue, and stop when a page adds no new listing_id values. Keep search_session_id stable for the whole sweep.

OfferUp Search API Documentation 1 credit/request

Endpoint

Request preview
GET
https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted
Auth header
x-api-key
Cost
1 credit/request
Response preview
200 OK
{
    "success": true,
    "query": "iphone",
    "location": {
        "zipcode": "77002",
        "lat": null,
        "lon": null,
        "radius": 30
    },
    "filters_applied": {
        "sort": "-posted"
    },
    "results": [
        {
...

Parameters

Start with the required fields, then add optional filters only when your use case needs them.

Runnable path

This endpoint has no required query parameters.

13 optional filters available.

q string Optional

Keyword search, e.g. "iphone".

Example value iphone
zipcode string Optional

5-digit US zip code to search around. Required unless lat and lon are given.

Example value 77002
lat string Optional

Latitude. Must be sent together with lon.

Example value example
lon string Optional

Longitude. Must be sent together with lat.

Example value example
radius string Optional

Search radius in miles around the location. OfferUp only honours 5, 10, 20, 30 or 50; any other value is snapped to a different distance behind a 200, so it is rejected.

Example value 30
cid string Optional

Category id from the categories endpoint, e.g. "5.1". Use this for keyword-free category browsing.

Example value example
category_id string Optional

Category filter. Requires q; without a keyword OfferUp drops it silently. Cannot be combined with cid.

Example value 1234567890
price_min string Optional

Minimum price in USD.

Example value example
price_max string Optional

Maximum price in USD.

Example value example
condition string Optional

Condition filter: NEW, OPEN_BOX, REFURBISHED, USED, BROKEN, OTHER.

Example value example
sort string Optional

Sort order: best_match, -posted, distance, price, -price.

Example value -posted
page_cursor string Optional

Opaque cursor from a previous page. Never construct this yourself.

Example value example
search_session_id string Optional

Session id from a previous page. Keep it stable across a paginated sweep.

Example value 1234567890

Request Examples

PHP
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => "https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => [
        "x-api-key: YOUR_API_KEY_HERE"
    ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #:" . $err;
} else {
    echo $response;
}
PHP
<?php

use Illuminate\Support\Facades\Http;

$response = Http::timeout(30)
    ->withHeaders(['x-api-key' => 'YOUR_API_KEY_HERE'])
    ->get('https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted');

if ($response->successful()) {
    echo $response->body();
} else {
    echo "Error: " . $response->status();
}
JavaScript
const options = {
    method: 'GET',
    headers: {
        'x-api-key': 'YOUR_API_KEY_HERE'
    }
};

fetch('https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted', options)
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.text();
    })
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));
JavaScript
const axios = require('axios');

const options = {
    method: 'GET',
    url: 'https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted',
    headers: {
        x-api-key: 'YOUR_API_KEY_HERE',
    }
};

try {
    const response = await axios(options);
    console.log(response.data);
} catch (error) {
    console.error('Error:', error.message);
}
Ruby
require 'net/http'
require 'uri'

uri = URI.parse("https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri.request_uri)
request['x-api-key'] = 'YOUR_API_KEY_HERE'

begin
    response = http.request(request)
    puts response.body
rescue => e
    puts "Error: #{e.message}"
end
Python
import http.client
import json

conn = http.client.HTTPSConnection("scrappa.co")

headers = {
    'x-api-key': 'YOUR_API_KEY_HERE',
}

try:
    conn.request("GET", "/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted", headers=headers)
    res = conn.getresponse()
    data = res.read()
    print(data.decode("utf-8"))
except Exception as e:
    print(f"Error: {e}")
finally:
    conn.close()
Python
import requests

headers = {
    'x-api-key': 'YOUR_API_KEY_HERE',
}

try:
    response = requests.get('https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted', headers=headers)
    response.raise_for_status()
    print(response.text)
except requests.exceptions.RequestException as e:
    print(f"Error: {e}")
Java
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;

public class ApiExample {
    public static void main(String[] args) {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
            .url("https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted")
        .addHeader("x-api-key", "YOUR_API_KEY_HERE")
            .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                System.out.println(response.body().string());
            } else {
                System.out.println("Error: " + response.code());
            }
        } catch (IOException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}
Go
package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {
    client := &http.Client{}
    req, err := http.NewRequest("GET", "https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted", nil)
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }
    req.Header.Set("x-api-key", "YOUR_API_KEY_HERE")

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error making request:", err)
        return
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }

    fmt.Println(string(body))
}
Terminal
#!/bin/bash

curl -X GET \
    -H "x-api-key: YOUR_API_KEY_HERE" \
    "https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted"
C#
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY_HERE");

        try
        {
            var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted"));
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}
TypeScript
import axios from 'axios';

async function run(): Promise<void> {
    try {
        const response = await axios({
            method: 'GET',
            url: 'https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted',
            headers: {
        'x-api-key': 'YOUR_API_KEY_HERE',
            },
        });

        console.log(response.data);
    } catch (error) {
        console.error('Error:', error);
    }
}

void run();
RUST
use reqwest::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    let response = client
        .get("https://scrappa.co/api/offerup/search?q=iphone&zipcode=77002&radius=30&sort=-posted")
        .header("x-api-key", "YOUR_API_KEY_HERE")
        .send()
        .await?;

    println!("{}", response.text().await?);

    Ok(())
}

Response Schema

Example response fields are illustrative; inspect the JSON before integrating.

Example response fields

Scan these fields before integrating.

success query location filters_applied results count page_cursor search_session_id +7 more

Common results fields

listing_id title price location_name
JSON Response
200 OK
{
    "success": true,
    "query": "iphone",
    "location": {
        "zipcode": "77002",
        "lat": null,
        "lon": null,
        "radius": 30
    },
    "filters_applied": {
        "sort": "-posted"
    },
    "results": [
        {
            "listing_id": "9c1f6a2e-4b7d-4a51-9f0e-1d2c3b4a5e6f",
            "title": "iPhone 13 128GB Blue",
            "price": 320,
            "location_name": "Houston, TX",
            "is_firm_price": null,
            "flags": null,
            "image_url": "https://example.com/offerup/iphone-13.jpg",
            "vehicle_miles": null,
            "tile_id": "tile-1",
            "tile_type": "LISTING",
            "condition": null,
            "post_date": null,
            "category": null,
            "fulfillment_details": null,
            "hydrated": false
        }
    ],
    "count": 1,
    "page_cursor": "H4sIAAAA...",
    "search_session_id": "session-abc",
    "available_filters": [
        {
            "target": "CONDITION"
        },
        {
            "target": "PRICE"
        }
    ],
    "excluded_ad_tiles": 2,
    "hydrated": false,
    "pagination_note": "OfferUp repeats listings across pages and gives no end-of-results signal. Stop when a page returns no new listing_id values.",
    "detail_only_fields": [
        "condition",
        "post_date",
        "category",
        "fulfillment_details"
    ],
    "total_results": null,
    "total_results_note": "OfferUp never reports a result total, so none is returned. Do not infer one from page counts."
}

Generate Code with AI

Copy a ready-made prompt with all the endpoint details, parameters, and example responses. Paste it into ChatGPT, Claude, or any AI assistant to instantly generate working code.

Try It Live

Test this endpoint in our interactive playground with real data.