[OfferUp APIs](/docs/api.md#offerup) 

# OfferUp Search API

Search OfferUp listings near a US city and state or ZIP code. Location is resolved automatically. Returns prices, locations, images and pickup information with cursor pagination. Seller ratings and cancellations are not included in OfferUp's search feed.

 POST `/v1/offerup/search`

[Parameters](#parameters) [Response](#response) [Errors](#errors) [More examples](#more-examples) 

**Authenticate every request**

Send `Authorization: Bearer YOUR_API_KEY`. [Get an API key →](/docs/guides/authentication.md) 

## Parameters

Send these fields in a flat JSON body. Add `_run` to choose execution options.

- **`location` string Required**
  US city and state, such as Los Angeles, CA, or a 5-digit ZIP code. Coordinates are resolved automatically. Minimum length: 2. Maximum length: 200.

- **`query` string Required**
  Search words, such as iPhone 13 or Nintendo Switch. Minimum length: 1. Maximum length: 200.

- **`limit` integer Optional**
  Maximum results in this response. Default: 25. Minimum: 1. Maximum: 50.

- **`condition` string Optional**
  Filter by an allowed product condition. Allowed: "new", "open_box", "refurbished", "used", "for_parts", "other".

- **`cursor` string Optional**
  next_cursor from the previous response. Keep the other search inputs unchanged. Maximum length: 131072.

- **`max_price` number Optional**
  Maximum price in the marketplace currency. Minimum: 0. Maximum: 1000000.

- **`min_price` number Optional**
  Minimum price in the marketplace currency. Minimum: 0. Maximum: 1000000.

- **`radius_miles` integer Optional**
  Search radius in miles around the resolved location. Default: 30. Allowed: 5, 10, 20, 30, 50, 80.

- **`sort` string Optional**
  Sort order. Choose an allowed value below. Allowed: "best_match", "newest", "closest", "price_low", "price_high".

### Advanced & compatibility inputs 1

- **`category_id` string Optional**
  OfferUp category id, such as 1.2 for cell phones and accessories. Pattern: ^[0-9]{1,4}(?:\.[0-9]{1,4}){0,2}$.

## Response

Every response describes a run. Read `result` once `run_status` is `succeeded`.

- **`run_id`**
  The run to track. Keep it when contacting support.

- **`run_status`**
  Queued, running, waiting for sign-in, succeeded, or failed. Check this even after an HTTP 200.

- **`status_url`**
  Poll this URL while the run is pending. Keep the same run instead of starting another request.

- **`result`**
  The endpoint’s returned data. The example is illustrative; actual fields may be empty or unavailable.

- **`error`**
  On failure, inspect its code, message, retryable and action fields.

### View the complete output schema

```json
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "success",
    "source",
    "query",
    "url",
    "results",
    "count",
    "data",
    "max_results_requested",
    "pagination"
  ],
  "properties": {
    "url": {
      "type": [
        "string",
        "null"
      ]
    },
    "data": {
      "type": "object",
      "required": [
        "location",
        "latitude",
        "longitude",
        "radius_miles",
        "applied_query",
        "seller_trust_available"
      ],
      "properties": {
        "latitude": {
          "type": "number"
        },
        "location": {
          "type": "string"
        },
        "longitude": {
          "type": "number"
        },
        "radius_miles": {
          "type": "integer"
        },
        "applied_query": {
          "type": [
            "string",
            "null"
          ]
        },
        "seller_trust_available": {
          "const": false
        }
      },
      "additionalProperties": false
    },
    "count": {
      "type": "integer",
      "minimum": 0
    },
    "query": {
      "type": [
        "string",
        "null"
      ]
    },
    "source": {
      "const": "offerup-search"
    },
    "results": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "title",
          "url",
          "price",
          "currency",
          "location",
          "condition",
          "image_url",
          "firm_price",
          "local_pickup"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "price": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "currency": {
            "const": "USD"
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "condition": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "firm_price": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "local_pickup": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "additionalProperties": false
      }
    },
    "success": {
      "const": true
    },
    "pagination": {
      "type": "object",
      "required": [
        "cursor",
        "next_cursor",
        "has_next_page",
        "count",
        "strategy"
      ],
      "properties": {
        "count": {
          "type": "integer"
        },
        "cursor": {
          "type": [
            "string",
            "null"
          ]
        },
        "strategy": {
          "const": "cursor"
        },
        "next_cursor": {
          "type": [
            "string",
            "null"
          ]
        },
        "has_next_page": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    },
    "max_results_requested": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}
```

### View a polled run response

```json
{
  "run_id": "00000000-0000-4000-8000-000000000001",
  "task_key": "offerup-search",
  "task_name": "OfferUp Search API",
  "run_status": "succeeded",
  "status_url": "/v1/runs/00000000-0000-4000-8000-000000000001",
  "result": {
    "url": "https://example.com/item-1",
    "data": {
      "latitude": 51.5074,
      "location": "London",
      "longitude": -0.1278,
      "radius_miles": 1,
      "applied_query": "London",
      "seller_trust_available": false
    },
    "count": 1,
    "query": "London",
    "source": "offerup-search",
    "results": [
      {
        "id": "id-1",
        "url": "https://example.com/item-1",
        "price": "180",
        "title": "Stylish Shoreditch Loft with Balcony",
        "currency": "USD",
        "location": "London",
        "condition": "sample",
        "image_url": "https://example.com/item-1",
        "firm_price": true,
        "local_pickup": true
      }
    ],
    "success": true,
    "pagination": {
      "count": 1,
      "cursor": "cursor-page-1",
      "strategy": "cursor",
      "next_cursor": "cursor-page-1",
      "has_next_page": true
    },
    "max_results_requested": 1
  },
  "error": null
}
```

## Errors & retries

- `400` Fix the fields listed in `errors` before retrying.

- `428` Connect the requested website account using the returned sign-in link.

- `429` Wait for the duration in `Retry-After`.

- `5xx` Retry transient failures with backoff. Keep the request ID.

A failed run has its own error inside the response. Retry it only when `error.retryable` is true.

[Validation, pagination and recovery →](/docs/api/request-guide.md) 

## More examples

### Async request with polling

```bash
RUN=$(curl -s -X POST "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"London","location":"London"}')

RUN_ID=$(echo "$RUN" | jq -er '.run_id // empty') || { echo "$RUN" >&2; exit 1; }
POLL_DELAY=3

while true; do
  RESULT=$(curl -s -H "Authorization: Bearer YOUR_API_KEY" "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/runs/$RUN_ID")
  STATUS=$(echo "$RESULT" | jq -r '.run_status')
  if [[ "$STATUS" =~ ^(succeeded|failed|errored|cancelled|canceled|expired)$ ]]; then
    echo "$RESULT"
    break
  fi
  sleep "$(awk "BEGIN { print $POLL_DELAY * (0.9 + ($RANDOM % 21) / 100) }")"
  POLL_DELAY=$((POLL_DELAY < 30 ? POLL_DELAY * 2 : 30))
  [ "$POLL_DELAY" -gt 30 ] && POLL_DELAY=30
done
```

```javascript
const runResponse = await fetch("https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "query": "London",
  "location": "London"
}),
});

const runPayload = await runResponse.json();
if (!runResponse.ok) throw new Error(JSON.stringify(runPayload));
const runId = runPayload.run_id;
if (!runId) throw new Error("Run ID not found in response");
let pollDelayMs = 3000;

while (true) {
  const resultResponse = await fetch("https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/runs/" + runId, {
    headers: { Authorization: "Bearer YOUR_API_KEY" },
  });
  const result = await resultResponse.json().catch(() => ({}));
  if (resultResponse.status === 429 || [502, 503, 504].includes(resultResponse.status)) {
    const retryAfter = resultResponse.headers.get("retry-after");
    await new Promise((resolve) => setTimeout(resolve, retryAfter ? Number(retryAfter) * 1000 : pollDelayMs));
    continue;
  }
  if (!resultResponse.ok) throw new Error(`Status request failed: ${resultResponse.status}`);
  const status = result.run_status;
  if (["succeeded", "failed", "errored", "cancelled", "canceled", "expired"].includes(status)) {
    console.log(result);
    break;
  }
  await new Promise((resolve) => setTimeout(resolve, pollDelayMs * (0.9 + Math.random() * 0.2)));
  pollDelayMs = Math.min(pollDelayMs * 2, 30000);
}
```

```python
import random
import time
import requests

run_response = requests.post(
    "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
    "query": "London",
    "location": "London"
},
)
run_payload = run_response.json()
if not run_response.ok:
    raise RuntimeError(run_payload)
run_id = run_payload.get("run_id")
if not run_id:
    raise ValueError("Run ID not found in response")
poll_delay = 3.0

while True:
    result_response = requests.get(
        "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/runs/" + run_id,
        headers={"Authorization": "Bearer YOUR_API_KEY"},
    )
    if result_response.status_code == 429 or result_response.status_code in (502, 503, 504):
        retry_after = result_response.headers.get("Retry-After")
        time.sleep(float(retry_after) if retry_after else poll_delay)
        continue
    result_response.raise_for_status()
    result = result_response.json()
    status = result.get("run_status")
    if status in ("succeeded", "failed", "errored", "cancelled", "canceled", "expired"):
        print(result)
        break
    time.sleep(poll_delay * random.uniform(0.9, 1.1))
    poll_delay = min(poll_delay * 2, 30.0)
```

### Wait-for-results request

```bash
curl -X POST "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"London","location":"London","_run":{"async":false}}'
```

```javascript
const response = await fetch("https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "query": "London",
  "location": "London",
  "_run": {
    "async": false
  }
}),
});

const result = await response.json();
console.log(result);
```

```python
import requests

response = requests.post(
    "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
    "query": "London",
    "location": "London",
    "_run": {
        "async": False
    }
},
)
result = response.json()
print(result)
```

### Canonical & compatibility routes

You can also send the same JSON body to `/v1/tasks/offerup-search/runs`.

 Example request  Request mode Wait for resultsAsync + polling

```bash
curl -X POST "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"London","location":"London","_run":{"async":false}}'
```

```javascript
const response = await fetch("https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "query": "London",
  "location": "London",
  "_run": {
    "async": false
  }
}),
});

const result = await response.json();
console.log(result);
```

```python
import requests

response = requests.post(
    "https://api-ingress-worker-staging.browsable-staging.workers.dev/v1/offerup/search",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
    "query": "London",
    "location": "London",
    "_run": {
        "async": False
    }
},
)
result = response.json()
print(result)
```

Requests can return before a run finishes. Check run_status and poll status_url when needed.

### View example response

 Example response  Illustrative 

```json
{
  "run_id": "00000000-0000-4000-8000-000000000001",
  "task_key": "offerup-search",
  "task_name": "OfferUp Search API",
  "run_status": "succeeded",
  "status_url": "/v1/runs/00000000-0000-4000-8000-000000000001",
  "result": {
    "url": "https://example.com/item-1",
    "data": {
      "latitude": 51.5074,
      "location": "London",
      "longitude": -0.1278,
      "radius_miles": 1,
      "applied_query": "London",
      "seller_trust_available": false
    },
    "count": 1,
    "query": "London",
    "source": "offerup-search",
    "results": [
      {
        "id": "id-1",
        "url": "https://example.com/item-1",
        "price": "180",
        "title": "Stylish Shoreditch Loft with Balcony",
        "currency": "USD",
        "location": "London",
        "condition": "sample",
        "image_url": "https://example.com/item-1",
        "firm_price": true,
        "local_pickup": true
      }
    ],
    "success": true,
    "pagination": {
      "count": 1,
      "cursor": "cursor-page-1",
      "strategy": "cursor",
      "next_cursor": "cursor-page-1",
      "has_next_page": true
    },
    "max_results_requested": 1
  },
  "error": null
}
```

[Understand the response →](#response)

---
Source: https://browsable-api-ux-staging.pages.dev/docs/api/endpoints/post-offerup-search
