# Wallapop Scraper & Monitor: New Listing, Price Drop Alerts (`accountable_eel/wallapop-listing-lookup`) Actor

Wallapop scraper and monitor. Search by keyword or paste a Wallapop link, get every listing newest first, and have new listings and price drops posted to Discord, Slack or Telegram. You are billed only for listings you did not already have. No login, no API key.

- **URL**: https://apify.com/accountable_eel/wallapop-listing-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 $0.50 / 1,000 listing 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

## Wallapop Scraper & Monitor: New Listing, Price Drop Alerts

Search Wallapop by keyword or pasted link and get every listing, newest first, as clean rows: title, price, category, city, postal code, photos, shipping flags and a link. Turn on monitoring and the actor remembers what it has already returned, so each scheduled run delivers (and bills) only the **new listings and price drops**, and can post them straight to Discord, Slack or Telegram.

No login, no cookies and no API key are needed. This actor is not affiliated with Wallapop.

### What it does

- **One line, one search.** Type keywords (`bicicleta`, `iphone 13`) or paste a Wallapop search link. From a link it takes the keywords, price range, category and area; every other link filter is named in `ignoredSearchFilters` instead of being silently dropped.
- **Newest first, always.** Each search returns Wallapop's newest listings first, 40 per page, up to 400 per search per run.
- **Filters Wallapop applies for you** (so you never pay for rows you did not want): minimum and maximum price, category ID, and an area (latitude, longitude and a radius in km).
- **Filters the actor applies after fetching, before billing:** countries, cities, postal-code prefixes and words to skip. A listing dropped here is never remembered as seen and never billed.
- **Monitoring.** "Only return listings that are new, or cheaper, since the last run" turns a search into a watchlist. A quiet run costs one request, returns one summary row (or nothing, if you choose) and bills only the start fee.
- **Optional details.** "Fetch listing details" opens each returned listing (up to 100 per search) for views, favourites, conversations and condition. It is billed separately and only for listings whose details arrived.
- **Privacy.** Sellers appear as a one-way hash, never the raw seller ID. Phone numbers, emails and chat links are removed from descriptions. Coordinates are not returned.

### 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~wallapop-listing-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"searches":["bicicleta"]}'
   ```
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": [
    "bicicleta"
  ]
}
```

One search per line: plain keywords (bicicleta, iphone 13) or a pasted Wallapop search link. A link's keywords, price range, category and area are used; any other link filter is named in ignoredSearchFilters. Results are always newest first. Accepted formats: bicicleta, iphone 13, https://es.wallapop.com/search?keywords=bicicleta\&min_sale_price=100.

#### Searching one area (read this)

Wallapop's own newest-first search is **country-wide**: asking for Madrid does not narrow a newest-first search to Madrid, and one page can mix Valencia, Badajoz, Almería and Italy. Two ways to get an area:

1. **Give a centre and a radius** (`latitude`, `longitude`, `distanceKm`). Wallapop then returns only listings within that distance and the actor keeps newest-first order. This is the right choice for a city or region. (Madrid centre: 40.4168, -3.7038.)
2. **Filter by city or postal code after fetching.** City, postal code, region and country are filled on every row, so `cities`, `postalCodePrefixes` and `countries` work, but the actor first has to read the country-wide feed (up to 400 rows per search), so it is slower and a quiet area may return few rows.

#### Price drops

With monitoring on, a listing already seen whose price fell by at least the percentage you set (default 5%) comes back with `changeType: "price-drop"`, `previousPrice` and `priceDropPct`. Wallapop sorts by publication date, not by edit date, so price drops are noticed on listings that are still near the top of the newest-first list when the run reads it. A price drop on a listing buried deep in the feed is not seen.

### Example runs

**Saved-search alerts (for a reseller who wants new ads only):** change `searches` to your keyword.

```json
{ "searches": ["bicicleta"], "onlyNewSinceLastRun": true, "deltaName": "wallapop-new-listing-alerts" }
```

**Bargain finder (for a buyer with a budget):** change `maxPrice`; Wallapop applies it, so you pay only for matches.

```json
{ "searches": ["bicicleta"], "maxPrice": 100, "onlyNewSinceLastRun": true, "deltaName": "wallapop-bargain-finder" }
```

**New listings near Madrid (for a local buyer):** change `distanceKm`.

```json
{ "searches": ["bicicleta"], "latitude": 40.4168, "longitude": -3.7038, "distanceKm": 15, "onlyNewSinceLastRun": true, "deltaName": "wallapop-madrid-new-listings" }
```

### Alerts to Discord, Slack or Telegram

1. In Discord, open the channel's settings, then **Integrations**, then **Webhooks**, create a webhook and copy its URL. (Slack: an incoming webhook. Telegram: a bot token and a chat ID.)
2. Paste it into **Send alerts to**. For Telegram use `https://api.telegram.org/bot<token>/sendMessage?chat_id=<chat id>`.
3. Turn on **Only return listings that are new, or cheaper, since the last run** and give the watchlist a name.
4. Optional: tick **Seed silently** so the first run only remembers what is on Wallapop today and the channel is not flooded.
5. Save the actor as a Task and schedule it, for example every 10 minutes or hourly.

Each new listing or price drop arrives as one message with photo, title, price, place and link, with a `NEW` or `PRICE DROP -X%` badge. Messages are free, and they are posted only after the listing has been billed. A message that fails never fails the run: the row's `notifyErrors` says why. Above "Most alert messages per run" one "+N more" message sums up the rest. The webhook link is stored encrypted and never logged.

### Limits

- At most **400 listings per search per run** (40 per page), and at most 100 detail lookups per search.
- **No sold or removed signal.** Wallapop's search has no sold flag and a sold listing simply leaves the results; this actor does not report listings that disappear.
- **No seller pages.** It searches listings; it does not list one seller's items.
- Prices come back in the listing's own currency (euros on the rows seen).
- Wallapop can refuse a request. A refused run returns a `BLOCKED` row, costs nothing more and bills nothing; try again later. There is no residential or unblocker fallback.
- Results depend on what Wallapop's public search returns at the moment of the run.

### Output

One row per listing, for example:

| query | found | status | searchQuery | monitorStatus | listingCount | newCount | priceDropCount | url | title | price | currency | description | category | city | postalCode | region | countryCode | createdAt | shippable | imageUrl | listingId | sellerHash | isNew | changeType | firstSeenAt | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| bicicleta | true | OK | <search> | <run status> | <listings returned> | <new listings> | <price drops> | <listing link> | <title> | <price> | <currency> | <description> | <category> | <city> | <postal code> | <region> | <country> | <listed> | <shipping available> | <photo> | <listing id> | \<seller (hashed)> | \<is this listing new?> | <change> | <first seen on a run> | 1970-01-01T00:00:00.000Z |

A search that finds nothing, a refused request or an unusable line comes back as a row with `"found": false` and a status (`NOT_FOUND`, `BLOCKED`, `REQUEST_REJECTED` or `BAD_FORMAT`) and is never charged.

### Pricing

- **Listing returned**: $1 per 1,000 listings
- **Listing details added**: $1 per 1,000 listings

Plus a $0.003 start fee per run. Each event above is billed independently, only when it actually returns data — misses (`found:false`) are 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~wallapop-listing-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"searches":["bicicleta"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~wallapop-listing-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"searches":["bicicleta"]}` (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~wallapop-listing-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 "Wallapop scraper with new listing and price drop alerts" — the agent will find and run this actor.

# Actor input Schema

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

One search per line: plain keywords (bicicleta, iphone 13) or a pasted Wallapop search link. A link's keywords, price range, category and area are used; any other link filter is named in ignoredSearchFilters. Results are always newest first. Accepted formats: bicicleta, iphone 13, https://es.wallapop.com/search?keywords=bicicleta\&min_sale_price=100. You're only charged for the ones we actually find — a miss costs nothing.

## `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.

## `maxListingsPerSearch` (type: `integer`):

Wallapop returns 40 listings per page. Default 40, maximum 400 per search per run. You pay per listing returned, so this is also your budget control. With monitoring on, listings you already have do not count against it.

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

Optional. Sent to Wallapop as its own price filter, so listings below it are never fetched or billed.

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

Optional. Sent to Wallapop as its own price filter.

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

Optional. Wallapop's numeric category, for example 17000 for Bikes. Every returned listing carries its categoryId, so you can copy one from a first run. Sent to Wallapop, which only returns that category.

## `latitude` (type: `number`):

Optional. With longitude and a distance, Wallapop returns only listings within that many kilometres. This is the right way to search one area: Wallapop's own newest-first search is country-wide otherwise. Madrid is 40.4168.

## `longitude` (type: `number`):

Optional, used together with latitude. Madrid is -3.7038.

## `distanceKm` (type: `integer`):

Optional, needs latitude and longitude. Wallapop narrows the search to this distance.

## `countries` (type: `array`):

Optional. Two-letter codes, one per line: ES, IT, PT. Applied by the actor after Wallapop answers, before you are billed. Wallapop's search spans several countries and returns them mixed.

## `cities` (type: `array`):

Optional, one per line, accents and capitals ignored. Wallapop's newest-first search is country-wide, so the actor drops other cities after fetching and before billing. For a whole area it is faster to give latitude, longitude and a radius above.

## `postalCodePrefixes` (type: `array`):

Optional. A listing is kept when its postal code starts with one of these, for example 28 for Madrid province or 08 for Barcelona.

## `excludeWords` (type: `array`):

Optional, one per line. Checked against title and description, accents and capitals ignored. A listing dropped here is never remembered as seen and never billed.

## `onlyNewSinceLastRun` (type: `boolean`):

Turns a search into a watchlist. A listing is new when its Wallapop listing ID was not returned by a previous run of the same watchlist, and a price drop when its price fell by at least the percentage below since it was last seen. Unchanged listings are removed before you are billed, so a quiet run only costs the start fee and returns one summary row. Price drops are only seen on listings still near the top of the newest-first list. Schedule it every 10 minutes or hourly, and have each alert posted straight to Discord, Slack or Telegram with "Send alerts to" below.

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

Leave empty and one is derived from this run's searches and filters, so two schedules with different filters keep separate memories. Type your own name to keep one memory across a filter change. Naming a watchlist with the checkbox above off returns every listing but still marks each one new / price-drop / seen.

## `alertOnNew` (type: `boolean`):

With monitoring on, return listings this watchlist has never seen before.

## `alertOnPriceDrop` (type: `boolean`):

With monitoring on, return listings already seen whose price has dropped since the last sighting.

## `minPriceDropPct` (type: `integer`):

A listing must be at least this much cheaper than when it was last seen to count as a price drop.

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

The first run of a watchlist has nothing to compare with. Turn this on to remember everything it finds without returning or billing it, so a webhook does not get a hundred messages on day one. Alerts start from the second run.

## `quietRunOutput` (type: `string`):

A quiet run is a search with nothing new or cheaper since the last run. "One summary row" (the default) keeps the row with monitorStatus NO_CHANGES and listingCount 0. "Nothing" writes zero items, so a bot or integration reading the dataset never has to skip rows. A search that failed still returns its error row.

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

Optional. After the listings are billed, each new and price-drop listing is posted here as one message: photo, title, price, place and link, with a NEW / PRICE DROP -X% badge. 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 message that fails never fails the run (the row's notifyErrors says why). Stored encrypted, never logged or written to the results. Alerts only go out with monitoring on.

## `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. Above this, one "+N more" message says how many more listings are in the run's dataset, so a busy first run never floods the channel. 0 sends only that one summary message.

## `includeDetails` (type: `boolean`):

Off by default. When on, the actor opens each returned listing (up to 100 per search) for its view, favourite and conversation counts, condition and characteristics. Billed as a separate listing-details charge, only for listings whose details arrived. With monitoring on, only new and price-drop listings are opened.

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

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

## `expandRows` (type: `boolean`):

When on, each listing found gets its own row instead of being grouped under its search. You're still only charged once per search, no matter how many rows it produces.

## `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 configuration. The default (Apify datacenter proxy) is what this actor is built and tested on. Residential proxies are not needed.

## Actor input object example

```json
{
  "searches": [
    "bicicleta"
  ],
  "testRun": false,
  "onlyFound": false,
  "maxListingsPerSearch": 40,
  "countries": [],
  "cities": [],
  "postalCodePrefixes": [],
  "excludeWords": [],
  "onlyNewSinceLastRun": false,
  "deltaName": "",
  "alertOnNew": true,
  "alertOnPriceDrop": true,
  "minPriceDropPct": 5,
  "skipFirstRun": false,
  "quietRunOutput": "summary",
  "notifyFormat": "auto",
  "maxNotificationsPerRun": 20,
  "includeDetails": false,
  "columns": [
    "searchQuery",
    "monitorStatus",
    "listingCount",
    "newCount",
    "priceDropCount",
    "url",
    "title",
    "price",
    "currency",
    "description",
    "category",
    "city",
    "postalCode",
    "region",
    "countryCode",
    "createdAt",
    "shippable",
    "imageUrl",
    "listingId",
    "sellerHash",
    "isNew",
    "changeType",
    "firstSeenAt"
  ],
  "expandRows": true,
  "maxConcurrency": 2,
  "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": [
        "bicicleta"
    ],
    "countries": [],
    "cities": [],
    "postalCodePrefixes": [],
    "excludeWords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/wallapop-listing-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": ["bicicleta"],
    "countries": [],
    "cities": [],
    "postalCodePrefixes": [],
    "excludeWords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/wallapop-listing-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": [
    "bicicleta"
  ],
  "countries": [],
  "cities": [],
  "postalCodePrefixes": [],
  "excludeWords": []
}' |
apify call accountable_eel/wallapop-listing-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/wallapop-listing-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/bMZadgcV6lb1EOgUR/builds/XtG5I6FnxINBC1ey5/openapi.json
