# ShopGoodwill Sold Prices & New Listing Alerts (`accountable_eel/shopgoodwill-sold-lookup`) Actor

ShopGoodwill sold prices: closed auctions with their final price, bids and end time for any search, plus an optional median / quartile comps summary and new-listing alerts to Discord, Slack or Telegram. No login. Searches that find nothing are free.

- **URL**: https://apify.com/accountable_eel/shopgoodwill-sold-lookup.md
- **Developed by:** [Adriana Voss](https://apify.com/accountable_eel) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 item row returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## ShopGoodwill Sold Prices & New Listing Alerts

Search ShopGoodwill and get what the items really sold for. For each search you get one row per item (title, category, final price, bids, end time, a link) from the site's closed auctions, an optional comps summary (median, quartiles, low, high), and, if you schedule it, alerts to Discord, Slack or Telegram when a new listing appears. It uses ShopGoodwill's public search data, so there is no login and no API key. A search that finds nothing is free.

Typical uses: price a lot before you bid on it, check what a brand or model sells for, or watch a search for new listings under a price.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `searches` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~shopgoodwill-sold-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"searches":["pyrex"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

### Input

```json
{
  "searches": [
    "pyrex"
  ]
}
```

One search per line, the words you would type into ShopGoodwill's search box, such as "pyrex" or "vintage camera". Each search reads up to the number of items set below, 40 per page. Accepted formats: pyrex, vintage camera, le creuset.

### Example

Price pyrex from the last 30 days, add a summary row, and get a Discord message for every new live listing under $25 on later runs:

```json
{
  "searches": ["pyrex"],
  "mode": "sold",
  "maxItems": 40,
  "includeCompsSummary": true
}
```

Sold prices come back as `item` rows (`saleStatus: "sold"`) plus one `comps-summary` row with `count`, `soldWithBidsCount`, `medianUsd`, `p25Usd`, `p75Usd`, `lowUsd`, `highUsd`, `medianBids` and `newestEndTime`.

For the alert, save this as a Task and schedule it daily:

```json
{
  "searches": ["pyrex"],
  "mode": "live",
  "maxPrice": 25,
  "maxItems": 100,
  "deltaMode": true,
  "deltaName": "pyrex-under-25",
  "skipFirstRun": true,
  "alertOnChangeOnly": true,
  "notifyUrl": "https://discord.com/api/webhooks/<id>/<token>"
}
```

The first run remembers what is listed now (`skipFirstRun`, nothing returned or billed). Each later run returns, bills and posts only the listings it has not seen before.

### Output

Rows are `item` rows, plus one `comps-summary` row per search when you ask for it. For example:

| query | found | status | rowType | searchMode | itemId | url | title | category | categoryId | priceUsd | bids | saleStatus | endTime | startTime | sellerId | shippingUsd | totalMatching | keywordMatch | changeType | alertReason | previousPriceUsd | priceChangePct | checkedAt | count | soldWithBidsCount | medianUsd | p25Usd | p75Usd | lowUsd | highUsd | medianBids | newestEndTime | windowDays | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| pyrex | true | OK | <row type: item or comps-summary> | \<searched: sold (closed auctions) or live> | <shopgoodwill item id> | <item link> | <title> | <category path> | <category id> | \<final price (sold) or current bid (live), usd> | <number of bids> | \<sold, no-bids or live> | \<auction end time (shopgoodwill site time)> | \<auction start time (shopgoodwill site time)> | \<seller (goodwill store) id> | \<shipping price shown in the listing, usd> | <total items shopgoodwill matched for this search> | <which of your title keywords matched> | \<since last run: new or seen (monitoring only)> | \<alert reason: new-listing or price-below (monitoring only)> | \<price the last run saw, usd (monitoring only)> | \<price change since the last run, % (monitoring only)> | \<checked at (iso time)> | \<summary: items read> | \<summary: items with at least one bid> | \<summary: median price, usd> | \<summary: 25th percentile price, usd> | \<summary: 75th percentile price, usd> | \<summary: lowest price, usd> | \<summary: highest price, usd> | \<summary: median number of bids> | \<summary: newest end time read> | \<summary: days of closed auctions searched> | 1970-01-01T00:00:00.000Z |

A miss comes back as a row with `"found": false` and is never charged.

### Fields

| Field | Meaning |
| --- | --- |
| `itemId`, `url` | ShopGoodwill's item number and the item page link. |
| `title`, `category`, `categoryId` | The listing title and its category path. |
| `priceUsd` | Sold mode: the final price. Live mode: the current bid. |
| `bids` | Number of bids. |
| `saleStatus` | `sold` (closed with at least one bid), `no-bids` (closed with none, only if you ask for them) or `live`. |
| `endTime`, `startTime` | ShopGoodwill's own clock, with no time zone attached. |
| `sellerId` | The seller's number. ShopGoodwill does not return the shop name in search results. |
| `shippingUsd` | The shipping price the listing shows (often 0 or a token amount; check the item page). |
| `totalMatching` | How many items ShopGoodwill matched for the search, which can be far more than you asked for. |
| `changeType`, `alertReason`, `previousPriceUsd`, `priceChangePct` | Monitoring only (empty otherwise). |
| `keywordMatch` | Which of your "only keep titles containing" words matched. |

### Limits

- Sold mode reads closed auctions as ShopGoodwill's search returns them: newest end time first, 40 per page, 10 pages (400 items) per search, and at most 365 days back (30 days when you do not set a date).
- An auction that closed with no bids is not a sale, so it is left out unless you tick the box. The summary prices use only auctions that had a bid.
- A closed auction's price is the last bid. It does not include shipping, ShopGoodwill's buyer's premium or tax.
- Live mode lists the items that end last first, which is roughly newest first. A broad search can have more live items than you ask for, so new-listing alerts only see what was read; keep alert searches narrow or raise the item limit.
- The price filters and the category filter are applied by ShopGoodwill. The date, title and no-bid filters are applied after the fetch.
- Requests are spaced two seconds apart, so a 400-item search takes under a minute. The actor does not log in and never opens cart, account or checkout pages.
- Monitoring remembers up to 20,000 items per watchlist in a named store on your account; the oldest are forgotten first.

### Pricing

Pay-per-event. A flat per-run fee covers session/proxy warmup; you're billed
per item only when data is actually found and returned — see
`.actor/pay_per_event.json` for exact prices. A miss is never charged.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~shopgoodwill-sold-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"searches":["pyrex"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~shopgoodwill-sold-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"searches":["pyrex"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~shopgoodwill-sold-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"searches":["{{search}}"]}`, mapping the row's search into the `searches` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "ShopGoodwill Sold Prices & New Listing Alerts | Apify" — the agent will find and run this actor.

# Actor input Schema

## `searches` (type: `array`):

One search per line, the words you would type into ShopGoodwill's search box, such as "pyrex" or "vintage camera". Each search reads up to the number of items set below, 40 per page. Accepted formats: pyrex, vintage camera, le creuset. You're only charged for the ones we actually find — a miss costs nothing.

## `mode` (type: `string`):

Pick the option that matches what you need — you can leave this on the recommended setting.

## `categoryId` (type: `integer`):

Only items in this ShopGoodwill category. Top-level IDs: 1 Antiques, 15 Art, 336 Bath & Body, 99 Books, 2208 Bulk, 170 Cameras & Camcorders, 10 Clothing, 4 Collectibles, 7 Computers & Electronics, 8 Crafts & Hobbies, 195 For The Home, 33 Gaming, 14 Glass, 6 Jewelry & Gemstones, 113 Miscellaneous, 2 Movies & Music, 13 Musical Instruments, 215 Office Supplies, 34 Pet Supplies, 115 Religious Items, 364 Science & Education, 18 Seasonal & Holiday, 12 Sports, 20 Tableware and Kitchenware, 114 Tools, 9 Toys/Dolls/Games, 23 Transportation, 427 Travel/Luggage, 468 Wedding. Sub-category IDs (the category_id column of a row you already have) work too.

## `minPrice` (type: `integer`):

Skips items priced below this many dollars (the final price for sold items, the current bid for live ones).

## `maxPrice` (type: `integer`):

Skips items priced above this many dollars. Use it with new-listing alerts to watch only the cheap ones.

## `endedAfter` (type: `string`):

Sold mode only. Keeps auctions that ended on or after this date (ShopGoodwill's own clock, no time zone), and searches back exactly that far. Empty searches the last 30 days; the furthest back is 365 days.

## `maxItems` (type: `integer`):

Up to 400 (ShopGoodwill returns 40 per page, so 10 pages). You are billed per item row returned.

## `includeCompsSummary` (type: `boolean`):

One extra row per search with the item count, median, 25th / 75th percentile, low, high and median bids of the items read. In sold mode the prices use only auctions that had a bid. Billed as one summary event.

## `includeUnsold` (type: `boolean`):

Sold mode only. Auctions with no bids are not sales (their price is just the starting bid), so they are left out unless you tick this. They still count in the summary's item count.

## `deltaMode` (type: `boolean`):

Each run compares every item with the items this search returned on earlier runs and fills in 'Since last run', the earlier price and the alert reason. Schedule it daily, then combine with 'Only return alerts' and 'Send alerts to'.

## `deltaName` (type: `string`):

Leave empty and one is derived from the mode, category and price limits. Type your own name to keep one memory across changes, or to run several independent watchlists.

## `alertOn` (type: `string`):

New listing: every item this search has not returned before. Price at or below: an item whose price is at or below the line below, when it is new or was above the line last time.

## `alertBelowUsd` (type: `integer`):

Used when 'Alert on' is 'Price at or below my line'. Whole dollars.

## `alertOnChangeOnly` (type: `boolean`):

With monitoring on, items that are not an alert are not returned and not billed, and a run with nothing to alert on bills nothing (the summary row is skipped too).

## `skipFirstRun` (type: `boolean`):

The first run of a search has nothing to compare with, so every item would be new. Turn this on to remember them without returning or billing them, so alerts start from the second run.

## `notifyUrl` (type: `string`):

Optional, needs monitoring on. After an alert row is billed it is posted here as one message with the title, price, bids, end time and item link. Discord or Slack: paste the channel's webhook URL. Telegram: https://api.telegram.org/bot<token>/sendMessage?chat_id=<chat id>. Any other URL receives JSON. Messages are free, and a failed message never fails the run (the row's notifyErrors says why). Stored encrypted, never logged or written to the results.

## `notifyFormat` (type: `string`):

Leave on 'Detect from the link': discord.com links get Discord messages, hooks.slack.com Slack, api.telegram.org Telegram, anything else JSON. Pick one only to override that.

## `maxNotificationsPerRun` (type: `integer`):

Up to 100. After this, one 'more alerts' message says the rest are in the run's dataset. 0 sends only that one message.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Keeps an item only if its title contains at least one of these words. Keep the 'Which of your title keywords matched' column selected when you use it.

## `excludeKeywords` (type: `array`):

Optional. Drops any item whose title contains one of these words, such as "lot" or "parts".

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

## `proxyConfiguration` (type: `object`):

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "searches": [
    "pyrex"
  ],
  "mode": "sold",
  "endedAfter": "",
  "maxItems": 40,
  "includeCompsSummary": true,
  "includeUnsold": false,
  "deltaMode": false,
  "deltaName": "",
  "alertOn": "new-listing",
  "alertOnChangeOnly": false,
  "skipFirstRun": false,
  "notifyFormat": "auto",
  "maxNotificationsPerRun": 20,
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "columns": [
    "rowType",
    "searchMode",
    "itemId",
    "url",
    "title",
    "category",
    "categoryId",
    "priceUsd",
    "bids",
    "saleStatus",
    "endTime",
    "startTime",
    "sellerId",
    "shippingUsd",
    "totalMatching",
    "keywordMatch",
    "changeType",
    "alertReason",
    "previousPriceUsd",
    "priceChangePct",
    "checkedAt",
    "count",
    "soldWithBidsCount",
    "medianUsd",
    "p25Usd",
    "p75Usd",
    "lowUsd",
    "highUsd",
    "medianBids",
    "newestEndTime",
    "windowDays"
  ],
  "maxConcurrency": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searches": [
        "pyrex"
    ],
    "includeCompsSummary": true,
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/shopgoodwill-sold-lookup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searches": ["pyrex"],
    "includeCompsSummary": True,
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/shopgoodwill-sold-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searches": [
    "pyrex"
  ],
  "includeCompsSummary": true,
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/shopgoodwill-sold-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/shopgoodwill-sold-lookup"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/cmTfLohrGTz5tHeaM/builds/pOQRJYiVawIix30Af/openapi.json
