# OLX New Listing Alert: Watch a Saved Search, No Login (`accountable_eel/olx-new-listing-alert`) Actor

OLX new listing alert: schedule a keyword search and get back only the ads posted since the last run, one row each, for olx.pl, olx.pt, olx.ro, olx.bg and olx.ua. The first run seeds silently so day one is not a flood. No login. A run with no new ads is free.

- **URL**: https://apify.com/accountable\_eel/olx-new-listing-alert.md
- **Developed by:** [Adrian 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 $3.04 / 1,000 new ad 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

## OLX New Listing Alert: Watch a Saved Search, No Login

This is an **alert**, not a search dump. Give it a keyword search, put it on a schedule, and every
run returns **only the ads posted since the last run** — one row each. The first run seeds silently,
so day one is not a flood of everything already on OLX. A run where nothing new was posted returns a
single summary row and costs you the start fee alone.

The general-purpose [OLX Listing Scraper](https://apify.com/accountable_eel/olx-listing-lookup) can
do this too, but it ships as a full-search scraper: you have to turn monitoring on, turn silent
seeding on, and decide what to do about price drops and disappeared listings. Here that is the
default and the only thing the actor is shaped for. If you want everything an OLX search returns —
every current listing, price-drop tracking, the sold signal with sell-through rate, raw seller
names — use the parent actor instead; it is listed under **Related actors** at the bottom.

### Who it's for

- **Deal hunters and resellers** on a hot search ("iPhone 14", "rower gorski") who want the ad
  within minutes of it going up, not within a day.
- **Anyone replacing OLX's own saved-search emails** with something that lands in a spreadsheet, a
  channel or a bot instead of an inbox.
- **Automations.** One row per new ad, straight into Google Sheets, n8n, Make, Zapier or your own
  script. Nothing to deduplicate: the actor has already removed everything it showed you before.

### What this does that a plain OLX search doesn't

| | Plain search | This actor |
|---|---|---|
| Rows you get back | every match, every run | only the ads posted since last time |
| First run | every current listing, billed | seeded silently, free |
| Deduplication | yours to do | already done |
| Cost of a quiet run | you pay for every row again | start fee only |
| "How old is this ad?" | `createdAt` only | `changeType`, `isNew`, `firstSeenAt` as well |

### How the watchlist works

Each search line becomes a **watchlist**. The actor remembers every OLX listing ID it has returned,
in your own account's storage.

1. **First run.** Banks every current listing silently. One summary row, `monitorStatus: FIRST_RUN`,
   no charge. (Turn *Seed silently* off if you would rather have the current state of the search as
   your first result set, and are happy to pay for it.)
2. **Every run after.** An ad comes back only when its OLX listing ID has never been returned by
   this watchlist. The row carries `changeType: new`, `isNew: true` and `firstSeenAt`.
3. **A quiet run.** Nothing posted since last time, so you get one row with
   `monitorStatus: NO_NEW_ROWS` and `listingCount: 0`.

Two schedules with different countries or price filters keep **separate** memories. Give a
*Watchlist name* to pin one memory across a settings change, or to have two schedules share one.

Want price cuts on ads you have already seen? Turn on *Also return price drops on ads you have
already seen* and set a minimum percentage. Those rows arrive marked `changeType: price-drop`.

### Countries

Five OLX country sites, picked with one dropdown: **olx.pl** (Poland, the default and the largest),
**olx.pt** (Portugal), **olx.ro** (Romania), **olx.bg** (Bulgaria) and **olx.ua** (Ukraine). Prices
come back in that country's own currency (PLN, EUR, RON, BGN, UAH), and each country keeps its own
watchlist memory even when two schedules share a name.

### What you get

One row per new ad:

- **The ad** — `listingId`, `title`, `price`, `currency`, `city`, `region`, `country`, `url`.
- **The seller** — `isBusiness`, `sellerType`, and a hashed `sellerHash` so you can spot the same
  reseller across listings. No names, no phone numbers, no chat flags.
- **Timing** — `createdAt` and `postedAt` from OLX, plus `firstSeenAt`, `changeType` and `isNew`
  from the watchlist.
- **The search** — `searchQuery`, `listingCount`, `totalAvailable`, `truncated`, `newCount`,
  `monitorStatus`, `medianPrice` and `medianPriceCurrency`, so every row tells you whether this ad
  is cheap for the search it came from.

### Price

- **New ad returned**: $4 per 1,000 listings

Plus a $0.00005 start fee per run. Each event above is billed independently, only when it actually returns data — misses (`found:false`) are never charged.

- **New ads:** $4 per 1,000 rows returned, plus a $0.00005 start fee per run.
- **A quiet run costs the start fee only.** No new ads means no rows, and no rows means no charge —
  even though the actor still fetched and compared every page.
- **The first run is free** while *Seed silently* is on, however many listings it banks.
- A search that returns nothing, or a country that refuses the run, is never billed.

Worked example. One watchlist checked every 15 minutes on a search where about 10 new ads appear a
day: 10 × 30 = 300 rows a month, **about $1.20 plus 2,880 start fees ($0.14)** — roughly **$1.34 a
month**. Checking more often costs almost nothing; what you pay for is ads.

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

Tips:

- **Check often, it is nearly free.** A run with no new ads costs $0.00005. Every 10 to 15 minutes is
  a reasonable cadence for a competitive search.
- **One search per line, not one giant query.** Each line keeps its own memory, so "iphone 14" and
  "iphone 15" never contaminate each other.
- **Use price filters to cut noise, not the keyword.** `minPrice` and `maxPrice` are applied before
  billing, so an ad outside your range never costs you a row.
- `includeKeywords` and `excludeKeywords` also match before billing — "uszkodzony" or "na czesci" in
  `excludeKeywords` keeps broken items out for free.

### How to filter

Every filter below is optional and off by default — leave it out and you get the exact same new-ad
alerts as before. `minPrice`/`maxPrice` already existed; this section adds `cities`,
`includeKeywords` and `excludeKeywords`. `availability` and `sortBy` are not offered on this actor:
OLX has no reserved/sold state to report (only a delta-derived "gone", which is not a sale), and
alerts are always newest-first by construction.

**1. Only new ads in Warsaw, price capped:**

```json
{
  "searches": ["iphone 14"],
  "country": "pl",
  "cities": ["warszawa"],
  "maxPrice": 3000
}
```

`cities` is a case-insensitive "contains" match against the ad's own city/region — leave it empty
to watch every location.

**2. Broken or for-parts phones excluded:**

```json
{
  "searches": ["iphone 14"],
  "excludeKeywords": ["uszkodzony", "na czesci"]
}
```

A title containing any of these is dropped before billing, so a broken listing never costs you a
row.

**3. Only listings whose title mentions "pro" or "max":**

```json
{
  "searches": ["iphone 14"],
  "includeKeywords": ["pro", "max"]
}
```

`includeKeywords` keeps a listing only if its title contains at least one of the words — combine it
with `excludeKeywords` to both require and forbid terms in the same run.

### Input

```json
{
  "searches": [
    "iphone 14"
  ]
}
```

One search per line: any keywords, e.g. "iphone 14", "rower gorski", "mieszkanie warszawa". Each line becomes a watchlist: the first run remembers what is already on OLX, and every run after it returns only the ads posted since. No login required. Pick the OLX country below (Poland by default). Accepted formats: iphone, rower gorski, mieszkanie warszawa.

### Sample output

| query | found | status | searchQuery | listingCount | totalAvailable | truncated | newCount | priceDropCount | goneCount | monitorStatus | sellThroughRate | medianPrice | medianPriceCurrency | listings | listingId | title | price | currency | city | region | country | isBusiness | sellerType | sellerHash | isPromoted | createdAt | postedAt | changeType | isNew | previousPrice | priceDropPct | firstSeenAt | lastSeenAt | daysListed | url | listingStatus | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| iphone 14 | true | OK | <search> | <new ads returned> | <total matching on olx> | <more results were available> | <new listings this run> | <price drops this run> | \<gone since last run (delisted, most often sold)> | \<monitoring status (quiet / seeded runs)> | \<sell-through rate, watchlist lifetime (0-1)> | \<median price of the search (local currency)> | <currency of the median price> | \<all new ads found (full list)> | <olx listing id> | <title> | \<price (local currency)> | <currency> | <city> | <region> | <olx country> | <business seller> | \<seller type (business / private)> | \<seller code (hashed, no name)> | \<promoted / sponsored listing> | <published> | \<posted (olx timestamp)> | \<new / price-drop / gone / seen> | \<is this listing new?> | <previous price> | \<price drop %> | <first seen on a run> | <last seen on a run> | \<days listed (first to last seen)> | <listing link> | \<status (marketplace-filters standard: available/reserved/sold)> | 1970-01-01T00:00:00.000Z |

A real row from the live smoke test (`iphone 14`, olx.pl, 5 ads checked, 2026-09-26). This is what
run one looks like with *Seed silently* on: the watchlist is banked, nothing is charged, and alerts
start next run.

```json
{
  "query": "iphone 14",
  "found": true,
  "status": "OK",
  "searchQuery": "iphone 14",
  "listingCount": 0,
  "totalAvailable": 1000,
  "truncated": true,
  "newCount": 0,
  "priceDropCount": 0,
  "goneCount": 0,
  "monitorStatus": "WATCHLIST_SEEDED",
  "sellThroughRate": 0,
  "medianPrice": 1349.5,
  "medianPriceCurrency": "PLN",
  "scrapedAt": "2026-09-26T00:07:19.619Z"
}
```

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

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~olx-new-listing-alert/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"searches":["iphone 14"]}` (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~olx-new-listing-alert/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 "OLX New Listing Alert: Only Ads Posted Since Last Run" — the agent will find and run this actor.

For a channel or an inbox, add an integration on the Task: trigger on "Apify: run succeeded" in n8n,
Make or Zapier and read the run's dataset. Setting *What a quiet run writes* is not offered here, so
a quiet run always leaves one summary row with `listingCount: 0` — filter on `listingCount > 0` in
your workflow to skip it.

### vs. alternatives

- **OLX's own saved-search email alerts.** Free, but they land in an inbox, cannot be filtered
  properly, and cannot feed a spreadsheet or a bot.
- **A general OLX scraper.** Returns every match every run. You pay for the same unchanged listings
  again and again, and you diff them yourself.
- **The parent actor,** [OLX Listing Scraper](https://apify.com/accountable_eel/olx-listing-lookup).
  Same engine, everything switched on: every current listing, price-drop tracking, the sold signal
  (`changeType: gone`, `gonePresumedSold`, days listed) with a watchlist sell-through rate, and raw
  seller names on request. Use it when you want the whole search; use this one when you want what's
  new.

### Data & privacy

- **No login, no cookies, no account.** There is no input field that could take an OLX session, and
  none is needed: everything here comes from OLX's public search results.
- **No seller names or contact details.** This variant does not offer the parent's raw-seller-info
  switch, so `sellerName` is not returned at all. OLX's own API exposes phone and chat contact
  flags; this actor never reads or outputs them.
- **The watchlist memory holds listing IDs and prices only**, in your own account's storage, and
  only for the watchlists you run.

### FAQ

**Nothing came back on the first run. Is it broken?**
No, that is the design. The first run banks what is already on OLX silently and returns one summary
row (`monitorStatus: FIRST_RUN`). Alerts start from the second run. Turn *Seed silently* off to get
the current state of the search instead.

**How does it know an ad is new?**
By OLX's own listing ID. An ad is new the first time this watchlist returns that ID — so a relisted
item with a fresh ID counts as new, and an edited ad does not.

**Can I get price drops as well?**
Yes — turn on *Also return price drops on ads you have already seen* and set the minimum percentage.

**Does it tell me when something sold?**
No. Disappeared-listing detection lives in the parent actor; this one is about ads appearing. See
**Related actors**.

**How often should I run it?**
Every 10 to 15 minutes for a competitive search, hourly otherwise. A run that finds nothing costs
the start fee alone.

**Which countries?**
olx.pl, olx.pt, olx.ro, olx.bg and olx.ua — one dropdown, Poland by default.

### Related actors

- **[OLX Listing Scraper: Poland Classifieds Data](https://apify.com/accountable_eel/olx-listing-lookup)**
  — the parent, and the full-capability version: every current listing on a search, price-drop
  tracking, the sold signal with `gonePresumedSold`, days listed and watchlist sell-through rate,
  raw seller names on request, and the same five countries.
- **[Vinted Price Monitor](https://apify.com/accountable_eel/vinted-price-monitor)** — the price-drop
  half of the same idea, on Vinted, with Discord, Slack and Telegram delivery.
- **[Kleinanzeigen Search: New Listing & Price Alerts](https://apify.com/accountable_eel/kleinanzeigen-search-lookup)**
  — the same monitoring idea on Germany's biggest classifieds site.

# Actor input Schema

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

One search per line: any keywords, e.g. "iphone 14", "rower gorski", "mieszkanie warszawa". Each line becomes a watchlist: the first run remembers what is already on OLX, and every run after it returns only the ads posted since. No login required. Pick the OLX country below (Poland by default). Accepted formats: iphone, rower gorski, mieszkanie warszawa. You're only charged for the ones we actually find — a miss costs nothing.

## `country` (type: `string`):

Which OLX country site to search. Defaults to olx.pl (Poland), so existing tasks and integrations that don't set this keep working exactly as before.

## `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. One keyword per line, case-insensitive. A listing is kept only if its title contains at least one of these. Leave empty to keep every title.

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

Optional. One keyword per line, case-insensitive. A listing whose title contains any of these is dropped. Leave empty to drop nothing.

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

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

Optional. Drop listings priced below this, in that country's own currency (PLN, EUR, RON, BGN, UAH). Leave empty for no minimum.

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

Optional. Drop listings priced above this, in that country's own currency (PLN, EUR, RON, BGN, UAH). Leave empty for no maximum.

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

Optional. One city or region name per line, case-insensitive, matched as a "contains" against the listing's own city/region. Leave empty to keep every location. Applied while paging, so the actor reads further pages until it has enough matches.

## `maxListingsPerQuery` (type: `integer`):

OLX returns up to 40 listings per page; this actor pages further if you ask for more. This is how deep the watchlist looks, not how many rows you get back: you only pay for the ads that are actually new.

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

On by default: this is what the actor is for, and the buyer-facing replacement for OLX's own saved-search email alerts. An ad counts as new when its OLX listing ID has not been returned by a previous run of the same watchlist. Already-seen listings are dropped before you are billed, so a quiet run costs only the start fee. The first run banks what it finds silently (see "Seed silently" below) and alerting starts from the second run. Turn this off to get every matching listing instead, like an ordinary search.

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

Leave empty and we derive one from this run's country and search settings, so two schedules with different settings keep separate memories. Type your own name to keep one memory across a settings change, or to have two schedules share one; a shared name still keeps different countries' memories apart.

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

On by default, and the whole point of this actor: ads whose OLX listing ID this watchlist has never seen before.

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

On by default. The first run of a watchlist would otherwise return every current listing as "new" and bill you for all of them. With this on it banks them silently and starts alerting from the second run. Turn it off if you want the current state of the search as your first result set, and are happy to pay for it.

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

Off by default, so your results stay new ads only. Turn it on to also get already-seen listings whose price fell by at least the percentage below, each marked price-drop rather than new. They are billed like any other row.

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

Only used when "Also return price drops" above is on. A listing must drop by at least this percentage since it was last seen to be reported.

## `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 config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "searches": [
    "iphone 14"
  ],
  "country": "pl",
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "cities": [],
  "maxListingsPerQuery": 52,
  "deltaMode": true,
  "deltaName": "",
  "alertOnNew": true,
  "skipFirstRun": true,
  "alertOnPriceDrop": false,
  "minPriceDropPct": 5,
  "columns": [
    "searchQuery",
    "listingCount",
    "totalAvailable",
    "truncated",
    "newCount",
    "priceDropCount",
    "goneCount",
    "monitorStatus",
    "sellThroughRate",
    "medianPrice",
    "medianPriceCurrency",
    "listings",
    "listingId",
    "title",
    "price",
    "currency",
    "city",
    "region",
    "country",
    "isBusiness",
    "sellerType",
    "sellerHash",
    "isPromoted",
    "createdAt",
    "postedAt",
    "changeType",
    "isNew",
    "previousPrice",
    "priceDropPct",
    "firstSeenAt",
    "lastSeenAt",
    "daysListed",
    "url",
    "listingStatus"
  ],
  "expandRows": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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": [
        "iphone 14"
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
    "cities": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/olx-new-listing-alert").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": ["iphone 14"],
    "includeKeywords": [],
    "excludeKeywords": [],
    "cities": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/olx-new-listing-alert").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": [
    "iphone 14"
  ],
  "includeKeywords": [],
  "excludeKeywords": [],
  "cities": []
}' |
apify call accountable_eel/olx-new-listing-alert --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/olx-new-listing-alert"
        }
    }
}
```

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/Ky62PCjmhEu7uXy82/builds/ZVqQSt0TR2yEvpgnz/openapi.json
