# OLX + Otomoto i Otodom: oferty, 392 filtry, miasto z promieniem (`compelling_queue_klo/olx-poland-scraper`) Actor

Pobiera oferty z OLX.pl: kategoria, podkategoria lub marka i model auta, miasto z promieniem w km i do 392 filtrów OLX (cena, rok, przebieg, metraż, typ umowy i inne). Opcjonalnie wzbogaca oferty danymi z Otomoto i Otodom. Do 1000 ofert na przebieg.

- **URL**: https://apify.com/compelling_queue_klo/olx-poland-scraper.md
- **Developed by:** [Kacper](https://apify.com/compelling_queue_klo) (community)
- **Categories:** E-commerce, Lead generation, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 listing / ofertas

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 Polska + Otomoto i Otodom: oferty, do 392 filtrów, miasto z promieniem / OLX Poland listings scraper

**Polski** | [English below](#english)

### Po polsku

Aktor pobiera oferty z **OLX.pl** (Polska): z wybranej kategorii lub podkategorii (także marki samochodu), opcjonalnie dla jednego miasta z **promieniem w km** i z **filtrami OLX (do 392 różnych kluczy** w 19 kategoriach głównych: cena, rok, przebieg, paliwo, metraż, typ umowy, stan i wiele innych). Opcjonalnie **wzbogaca oferty danymi z Otomoto i Otodom** (wyposażenie, specyfikacja, koszt miesięczny najmu). Limit: **do 1000 ofert na jeden przebieg**. Wynik trafia do datasetu Apify (tabela, JSON, CSV, Excel).

**Marka i model samochodu:** aktor nie ma osobnych pól „marka” i „model”. Marka to podkategoria (`subcategoryId`, np. Toyota = `206`), a model to filtr `filter_enum_model=corolla`. Szczegóły i gotowe przykłady: [Jak ustawić filtry krok po kroku](#filtry-krok-po-kroku). Najczęstsze filtry są opisane poniżej. Klucze i wartości filtrów każdej kategorii zwraca tryb `listFilters` (patrz niżej), a numery kategorii i marek są w sekcji [Skąd wziąć ID](#skad-wziac-id). Gdy wpiszesz zły klucz albo wartość, przebieg kończy się komunikatem (w logu i w statusie przebiegu) z listą poprawnych kluczy albo wartości dla wybranej kategorii.

#### Ważne zastrzeżenia

- Wyniki służą do **własnej analizy** (np. obserwacja rynku, własne poszukiwania). **Nie wolno ich odsprzedawać**, ani używać do pośrednictwa w sprzedaży cudzych ofert.
- **Limit ofert na przebieg**: domyślnie 100, maksymalnie 1000. Aktor nie eksportuje całej bazy OLX, a sam OLX nie udostępnia więcej niż ok. 1000 wyników na jedno zapytanie.
- Między zapytaniami do OLX jest przerwa co najmniej 1 sekundy (do Otomoto/Otodom, przy włączonym wzbogacaniu: co najmniej 2 sekundy).
- To nieoficjalne narzędzie. Nie jest powiązane z OLX i korzysta z publicznie dostępnego, nieudokumentowanego interfejsu serwisu, który OLX może zmienić lub zablokować w dowolnym momencie.
- Ty odpowiadasz za zgodność użycia danych z regulaminem OLX i przepisami (w tym o danych osobowych).

#### Jak użyć

1. Wybierz **Kategorię** (np. Praca).
2. Opcjonalnie ustaw **Miasto** i **Promień (km)**. Promień wymaga miasta. Dozwolone: 0, 2, 5, 10, 15, 20, 30, 50, 75, 100.
3. Ustaw **Limit ofert na przebieg** (domyślnie 100 przy wywołaniu przez API bez tego pola, maks. 1000). Formularz w Console jest wstępnie wypełniony próbnym wejściem: Praca, Wrocław +20 km, 20 ofert, bez wzbogacania; zmień te wartości na własne. Opcjonalnie włącz **Wzbogać oferty z Otomoto / Otodom** i ustaw **Limit wzbogaceń na przebieg** (patrz niżej).
4. Opcjonalnie dodaj **Filtry OLX** w formacie `klucz=wartość`, np.:
   - `owner_type=private` (tylko prywatni sprzedający),
   - `sort_by=created_at:desc` (najnowsze najpierw),
   - `filter_float_price:from=1000` i `filter_float_price:to=5000` (cena od-do).
     Błędny klucz albo wartość kończy przebieg komunikatem z listą poprawnych opcji. Wszystkie zasady i przykłady: [Jak ustawić filtry krok po kroku](#filtry-krok-po-kroku).
5. **Proxy**: domyślnie brak. Jeśli OLX odpowie błędem 403 albo 429, spróbuj `datacenter` albo `residential` (wymaga dostępu do tych grup proxy na koncie Apify; residential jest płatne za GB).
6. Uruchom aktora i pobierz wyniki z zakładki **Storage → Dataset**.

#### Przykładowe wejście

```json
{
  "category": "praca",
  "city": "Wrocław",
  "radiusKm": 20,
  "maxItems": 100,
  "filters": ["sort_by=created_at:desc"],
  "proxyType": "none"
}
```

<a id="filtry-krok-po-kroku"></a>

#### Jak ustawić filtry krok po kroku

1. **Wybierz kategorię** w polu `category` (tabela niżej). To jedna z 8 kategorii głównych.
2. **Zawęź do podkategorii**, jeśli chcesz mieć więcej filtrów: wpisz jej numer w `subcategoryId`. Filtry są sprawdzane względem **wybranej** (pod)kategorii, a kategorie główne mają ich mało (np. Motoryzacja: tylko cena, sortowanie, typ sprzedawcy). Samochody osobowe to `84`, marka Toyota `206`, Mieszkania na wynajem `15`, Słuchawki bezprzewodowe `1973`. Numery znajdziesz w sekcji [Skąd wziąć ID](#skad-wziac-id) albo trybem `listFilters`.
3. **Dodaj filtry** w polu `filters`. Każdy wpis to osobny tekst `klucz=wartość`, np. `filter_enum_petrol=diesel`. Wielkość liter wartości nie ma znaczenia, można podać kod (`diesel`) albo nazwę z OLX (`Diesel`). Nie dopisuj `[0]` do klucza.
4. **Zakresy** (cena, rok, przebieg, metraż) to dwa osobne wpisy: `klucz:from=OD` i `klucz:to=DO`. Wystarczy jeden z nich (np. tylko `filter_float_year:from=2015`). Liczby bez spacji i jednostek. Pola całkowite (rok, przebieg, moc) nie przyjmują ułamków.
5. **Kilka wartości** tego samego filtra to powtórzony klucz i działa jak „LUB”: `filter_enum_petrol=diesel` + `filter_enum_petrol=petrol` pokaże diesle i benzyny. Dla `sort_by` i `owner_type` można podać tylko jedną wartość.
6. **Pola „tak/nie”** wpisujesz jako `klucz=1` (np. `filter_bool_remote_recruitment=1`). `=0` aktor odrzuca, bo w OLX oznacza „tylko oferty bez tej cechy”.
7. **Miasto i promień**: `city` (nazwa, np. `Wrocław`; przy niejednoznacznej nazwie dopisz powiat: `Bardo, ząbkowicki`) i `radiusKm` (0, 2, 5, 10, 15, 20, 30, 50, 75, 100). Promień wymaga miasta.
8. **Uruchom z małym `maxItems`** (np. 20), sprawdź wyniki, potem zwiększ do maks. 1000. Zły klucz albo wartość kończy przebieg od razu komunikatem z listą poprawnych opcji i nic nie kosztuje.

**Marka i model samochodu.** Brak osobnych pól. Marka = `subcategoryId` marki (np. Toyota `206`, BMW, Audi: lista marek z numerami jest w sekcji [Skąd wziąć ID](#skad-wziac-id)), model = `filter_enum_model=<kod>` (np. `corolla`). Filtr modelu działa tylko razem z `subcategoryId` marki; przy `subcategoryId: 84` (wszystkie marki) zostanie odrzucony. Pozostałe filtry samochodów (paliwo, rok, przebieg...) działają zarówno dla `84`, jak i dla marki.

**Płaca w Pracy.** OLX nie ma w Pracy filtra „płaca od-do”. Wynagrodzenie dostajesz w polach `salaryFrom`, `salaryTo`, `salaryText` (jeśli oferta je podaje), a sortować po nim możesz: `sort_by=filter_float_salary_to:desc`.

##### Kategorie główne

| `category` | Kategoria | ID OLX |
|---|---|---|
| `praca` | Praca | 4 |
| `motoryzacja` | Motoryzacja | 5 |
| `nieruchomosci` | Nieruchomości | 3 |
| `elektronika` | Elektronika | 99 |
| `dom-ogrod` | Dom i Ogród | 628 |
| `moda` | Moda | 87 |
| `zwierzeta` | Zwierzęta | 103 |
| `sport-hobby` | Sport i Hobby | 767 |

OLX ma 19 kategorii głównych. Pozostałe (m.in. Dla Dzieci 88, Książki i Muzyka 751, Rolnictwo 757, Zdrowie i Uroda 3647, Firma i Przemysł 4269, Usługi 4371, Budowa i Remont 5216) nie są jeszcze dostępne w tym aktorze.

##### Najczęstsze filtry

Klucze działają po wpisaniu w `filters` jako `klucz=wartość` (zakresy: `klucz:from=` / `klucz:to=`). Dostępność zależy od (pod)kategorii.

**Wszędzie (prawie każda kategoria):** `sort_by` (`created_at:desc` najnowsze, `relevance:desc` domyślne, `filter_float_price:asc` / `:desc` cena), `owner_type` (`private` prywatne, `business` firmowe), `filter_float_price` (zakres, cena w zł).

**Motoryzacja > Samochody osobowe (`subcategoryId: 84`) i marki (np. `206`):**

| Klucz | Typ | Wartości |
|---|---|---|
| `filter_float_year` | zakres całkowity | rok produkcji, np. `:from=2015` |
| `filter_float_milage` | zakres całkowity | przebieg w km, np. `:to=150000` |
| `filter_float_enginesize` | zakres całkowity | pojemność silnika w cm³ |
| `filter_float_enginepower` | zakres całkowity | moc w KM |
| `filter_enum_petrol` | lista | `petrol` benzyna, `diesel`, `lpg`, `cng` (CNG i hybryda), `plugin-hybrid`, `electric` |
| `filter_enum_transmission` | lista | `manual`, `automatic` |
| `filter_enum_car_body` | lista | `sedan`, `hatchback`, `estate-car` (kombi), `suv`, `coupe`, `cabriolet`, `minibus`, `mvp`, `pickup`, `off-road-vehicle` |
| `filter_enum_condition` | lista | `notdamaged`, `damaged` |
| `filter_enum_drive` | lista | `front-wheel`, `rear-wheel`, `all-wheel-permanent`, `all-wheel-auto`, `all-wheel-lock` |
| `filter_enum_color` | lista | `bialy`, `czarny`, `szary`, `srebrny`, `niebieski`, `czerwony`... |
| `filter_enum_model` | lista | tylko przy `subcategoryId` marki, np. Toyota: `corolla`, `yaris`, `auris`... |

**Nieruchomości > Mieszkania > Wynajem (`15`) / Sprzedaż (`14`):**

| Klucz | Typ | Wartości |
|---|---|---|
| `filter_float_price` | zakres | cena w zł |
| `filter_float_m` | zakres | powierzchnia w m² |
| `filter_enum_rooms` | lista | `one` (kawalerka), `two`, `three`, `four` (4 i więcej) |
| `filter_enum_furniture` | lista | `yes`, `no` (umeblowane) |
| `filter_enum_floor_select` | lista | `floor_0` (parter), `floor_1`, `floor_2`... |
| `filter_enum_builttype` | lista | `blok`, `kamienica`, `apartamentowiec`, `loft`... |
| `filter_enum_market` | lista | tylko sprzedaż: `primary` pierwotny, `secondary` wtórny |
| `filter_enum_pets`, `filter_enum_winda`, `filter_enum_parking` | lista | tylko wynajem: `Tak`/`Nie`; parking: `w garażu`, `parking strzeżony`... |

**Praca (`category: praca`):**

| Klucz | Typ | Wartości |
|---|---|---|
| `filter_enum_type` | lista | wymiar pracy: `fulltime`, `parttime`, `halftime`, `seasonal`, `b2b` |
| `filter_enum_agreement` | lista | umowa: `part` (o pracę), `zlecenie`, `contract` (o dzieło), `selfemployment`, `practice`, `other` |
| `filter_enum_workplace` | lista | `on_site`, `remote_work_possibility` (zdalna), `hybrid`, `traveling` |
| `filter_enum_experience` | lista | `exp_yes`, `exp_no` (bez doświadczenia) |
| `filter_enum_availability` | lista | `shift_work`, `weekends_work`, `flexible_work` |
| `filter_enum_special_requirements` | lista | `sanitary_card`, `disability_certificate`, `student_status` |
| `filter_bool_remote_recruitment` | tak/nie | `=1` rekrutacja online |
| `sort_by` | jedna wartość | m.in. `filter_float_salary_to:desc` (pensja malejąco) |

**Elektronika:** zestaw zależy od podkategorii. Słuchawki bezprzewodowe (`1973`): `filter_enum_state` (`new`, `used`, `damaged`), `filter_enum_brand` (`apple`, `jbl`, `bose`, `sony`...), `filter_enum_type` (`in_ear`, `in_canal`, `on_ear`...), `filter_enum_etui` (`yes`/`no`), `filter_float_price`. Smartfony (`1839`): `filter_enum_state` (`new`, `refurbished`, `used`, `damaged`), `filter_enum_builtinmemory_phones`, `filter_enum_rammemory_phones`, `filter_enum_coloriphone`, `filter_enum_sim_options`. Laptopy (`1199`): `filter_enum_state`, `filter_enum_ramsize_laptops`, `filter_enum_disksize_laptops`, `filter_enum_operatingsystem_laptops`, `filter_enum_processorseries_laptops`.

##### Gotowe przykłady wejścia

**1. Toyota Corolla, diesel, rok 2015 i nowsza, przebieg do 150 tys. km, najnowsze najpierw:**

```json
{
  "category": "motoryzacja",
  "subcategoryId": 206,
  "maxItems": 100,
  "filters": [
    "filter_enum_model=corolla",
    "filter_enum_petrol=diesel",
    "filter_float_year:from=2015",
    "filter_float_milage:to=150000",
    "sort_by=created_at:desc"
  ]
}
```

**2. Praca we Wrocławiu i do 20 km dookoła, pełny etat, umowa o pracę, najwyższa płaca na górze:**

```json
{
  "category": "praca",
  "city": "Wrocław",
  "radiusKm": 20,
  "maxItems": 200,
  "filters": [
    "filter_enum_type=fulltime",
    "filter_enum_agreement=part",
    "sort_by=filter_float_salary_to:desc"
  ]
}
```

**3. Mieszkania do wynajęcia we Wrocławiu: cena 2000-3500 zł, 40-70 m², 2 lub 3 pokoje, umeblowane:**

```json
{
  "category": "nieruchomosci",
  "subcategoryId": 15,
  "city": "Wrocław",
  "radiusKm": 5,
  "maxItems": 100,
  "filters": [
    "filter_float_price:from=2000",
    "filter_float_price:to=3500",
    "filter_float_m:from=40",
    "filter_float_m:to=70",
    "filter_enum_rooms=two",
    "filter_enum_rooms=three",
    "filter_enum_furniture=yes",
    "owner_type=private"
  ]
}
```

**4. Słuchawki bezprzewodowe, nowe, do 400 zł:**

```json
{
  "category": "elektronika",
  "subcategoryId": 1973,
  "maxItems": 100,
  "filters": [
    "filter_enum_state=new",
    "filter_float_price:to=400",
    "sort_by=filter_float_price:asc"
  ]
}
```

**5. Samochody osobowe (wszystkie marki) od prywatnych, 2012+, automat, benzyna lub diesel, z danymi z Otomoto:**

```json
{
  "category": "motoryzacja",
  "subcategoryId": 84,
  "maxItems": 50,
  "enrichDetails": true,
  "maxEnrichments": 20,
  "filters": [
    "owner_type=private",
    "filter_float_year:from=2012",
    "filter_enum_transmission=automatic",
    "filter_enum_petrol=petrol",
    "filter_enum_petrol=diesel"
  ]
}
```

**6. Praca zdalna, bez doświadczenia, rekrutacja online, najnowsze:**

```json
{
  "category": "praca",
  "maxItems": 100,
  "filters": [
    "filter_enum_workplace=remote_work_possibility",
    "filter_enum_experience=exp_no",
    "filter_bool_remote_recruitment=1",
    "sort_by=created_at:desc"
  ]
}
```

<a id="skad-wziac-id"></a>

#### Skąd wziąć ID (kategorie, podkategorie, marki)

Numer (ID) wpisujesz w polu `subcategoryId`. Są trzy sposoby, żeby go znaleźć, bez zaglądania do kodu aktora:

| Sposób | Jak |
|---|---|
| 1. Listy w tej sekcji | poniżej: 19 kategorii głównych, podkategorie 1. poziomu, wybrane głębsze poziomy i wszystkie marki samochodów osobowych |
| 2. Tryb `listFilters` (bez opłat za oferty) | `{"mode": "listFilters"}` zwraca listę kategorii z ID. `{"mode": "listFilters", "search": "toyota"}` zwraca trafienia po nazwie z ID i ścieżką (Toyota = `206`), `"search": "wynajem"` pokazuje wszystkie „Wynajem” z ich ID. `{"mode": "listFilters", "subcategoryId": 84}` pokazuje filtry wybranego węzła |
| 3. Pełny katalog jednym przebiegiem | przebieg `{"mode": "listFilters"}` (bez `search` i `subcategoryId`) zapisuje w **Key-value store** przebiegu rekordy `ID_CATALOG` (JSON) i `ID_CATALOG_CSV` (CSV do Excela) ze **wszystkimi 3318 węzłami** drzewa OLX: `id`, `name`, `parentId`, `path`, `mainCategory`, `isBrand`. Pobierzesz je w Console (Storage → Key-value store) albo przez API (`.../key-value-stores/<id>/records/ID_CATALOG_CSV`) |

Dodatkowo każda oferta w wyniku ma pole `categoryId`: to ID najmniejszej kategorii tej oferty i można je wpisać w `subcategoryId`. Fragment `-CID<liczba>-` w adresie oferty (`url`) jest ID **kategorii głównej** (sprawdzone na ofertach z Pracy i Nieruchomości), więc ID podkategorii ani marki z niego nie odczytasz.

Listy poniżej są wygenerowane z drzewa kategorii zapisanego w aktorze (kopia z 2026-10-30; 3318 węzłów, z czego 253 to podkategorie 1. poziomu).

**Kategorie główne (19).** Pole `category` aktora obsługuje 8 z nich; resztę można obejrzeć w `listFilters`, ale nie pobierać.

| ID | Kategoria | `category` w aktorze |
|---|---|---|
| 3 | Nieruchomości | `nieruchomosci` |
| 4 | Praca | `praca` |
| 5 | Motoryzacja | `motoryzacja` |
| 87 | Moda | `moda` |
| 99 | Elektronika | `elektronika` |
| 103 | Zwierzęta | `zwierzeta` |
| 628 | Dom i Ogród | `dom-ogrod` |
| 767 | Sport i Hobby | `sport-hobby` |
| 88 | Dla Dzieci | nie (tylko przez listFilters) |
| 751 | Książki i Muzyka | nie (tylko przez listFilters) |
| 757 | Rolnictwo | nie (tylko przez listFilters) |
| 1151 | Oddam za darmo | nie (tylko przez listFilters) |
| 1816 | Noclegi | nie (tylko przez listFilters) |
| 3018 | Wypożyczalnia | nie (tylko przez listFilters) |
| 3647 | Zdrowie i Uroda | nie (tylko przez listFilters) |
| 4042 | Antyki i Kolekcje | nie (tylko przez listFilters) |
| 4269 | Firma i Przemysł | nie (tylko przez listFilters) |
| 4371 | Usługi | nie (tylko przez listFilters) |
| 5216 | Budowa i Remont | nie (tylko przez listFilters) |

**Podkategorie 1. poziomu** (`Nazwa=ID; ...`) w 8 kategoriach obsługiwanych przez aktora:

- **Praca** (`praca`, 4): Administracja biurowa=1421; Badania i rozwój=4222; Bankowość=4959; BHP i ochrona środowiska=4956; Budowa / remonty=1443; Dostawca, kurier miejski=2446; E-commerce (handel internetowy)=1710; Edukacja=53; Energetyka=3899; Finanse / księgowość=1423; Franczyza / Własna firma=1529; Fryzjerstwo, kosmetyka=1860; Gastronomia=1437; Hostessa, roznoszenie ulotek=2451; Hotelarstwo=1439; HR=1425; Inżynieria=4223; IT / telekomunikacja=56; Kadra kierownicza=1538; Kierowca=2445; Logistyka, zakupy, spedycja=2447; Marketing i PR=2519; Mechanika i lakiernictwo=2516; Montaż i serwis=2450; Nieruchomości=5421; Obsługa klienta i call center=2508; Ochrona=1213; Opieka=1861; Organizacja i obsługa imprez=4957; Pozostałe oferty pracy=65; Praca dodatkowa=4651; Praca od zaraz=5268; Praca sezonowa=4650; Praca za granicą=1481; Prace magazynowe=1797; Pracownik sklepu=3902; Praktyki / staże=1453; Prawo=5422; Produkcja=1447; Rolnictwo i ogrodnictwo=1712; Sprzedaż=2507; Sprzątanie=1449; Służby mundurowe=4958; Ubezpieczenia=4960; Wykładanie i ekspozycja towaru=2449; Zapraszamy seniorów=4735; Zdrowie=1862
- **Motoryzacja** (`motoryzacja`, 5): Budowlane=3560; Ciężarowe=3559; Części motocyklowe=1335; Części samochodowe=82; Dostawcze=3558; Motocykle i Skutery=81; Opony i Felgi=658; Pozostała motoryzacja=80; Przyczepy i Naczepy=3561; Samochody osobowe=84; Sprzęt car audio=1461; Wyposażenie i akcesoria samochodowe=4482
- **Nieruchomości** (`nieruchomosci`, 3): Biura i Lokale=710; Domy=1309; Działki=709; Garaże i Parkingi=124; Hale i Magazyny=1321; Mieszkania=1307; Nieruchomości za granicą=4514; Pozostałe nieruchomości=32; Stancje i Pokoje=11
- **Elektronika** (`elektronika`, 99): Fotografia=727; Gry i Konsole=93; Komputery=443; Pozostałe=451; Smartwatche i opaski=1856; Sprzęt  video=1979; Sprzęt AGD=1776; Sprzęt audio=1951; Telefony=1838; TV=1848
- **Dom i Ogród** (`dom-ogrod`, 628): Meble=565; Narzędzia=1193; Ogrzewanie=279; Ogród=584; Oświetlenie=3844; Pozostałe=293; Supermarket=4354; Wyposażenie wnętrz=1409
- **Moda** (`moda`, 87): Akcesoria=2717; Bielizna damska=2662; Bielizna męska=2663; Biżuteria=401; Buty damskie=2679; Buty męskie=2680; Czapki i kapelusze=2740; Do ślubu=2847; Odzież ciążowa=1319; Pozostałe=405; Torby i torebki=2749; Ubrania damskie=2526; Ubrania męskie=2528; Zegarki=626
- **Zwierzęta** (`zwierzeta`, 103): Akcesoria dla zwierząt=175; Akwarystyka=1293; Gryzonie i Króliki=1457; Karma i przysmaki=3881; Konie=1459; Koty=138; Pozostałe=140; Psy=139; Ptaki=1455; Terrarystyka=4033; Zaginione i znalezione=180
- **Sport i Hobby** (`sport-hobby`, 767): Bilety=1527; Fitness=771; Gry planszowe=1812; Hobby kreatywne=5504; Jeździectwo=1540; Militaria=1601; Pojazdy elektryczne=4309; Pozostałe=100; Rowery=461; Skating=1670; Sporty drużynowe=4970; Sporty ekstremalne=5488; Sporty towarzyskie=4963; Sporty walki=5481; Sporty wodne=1517; Sporty zimowe=769; Społeczność=6; Turystyka=465; Wędkarstwo=773

**Wybrane głębsze poziomy** (`Rodzic (ID): Nazwa=ID; ...`, rodzice oddzielone znakiem `|`). Przykłady: Mieszkania > Wynajem = `15`, Mieszkania > Sprzedaż = `14`, Samochody osobowe = `84`:

- **Nieruchomości**: Biura i Lokale (710): Sprzedaż=125; Wynajem=127; Zamiana=1405 | Domy (1309): Sprzedaż=18; Wynajem=20; Zamiana=22 | Działki (709): Sprzedaż=24; Wynajem=25; Zamiana=1311 | Garaże i Parkingi (124): Sprzedaż=1315; Wynajem=1313; Zamiana=1317 | Hale i Magazyny (1321): Sprzedaż=1329; Wynajem=1331 | Mieszkania (1307): Sprzedaż=14; Wynajem=15; Zamiana=16 | Pozostałe nieruchomości (32): Sprzedaż=1323; Wynajem=1325; Zamiana=1327
- **Motoryzacja**: Budowlane (3560): Dźwigi, żurawie, podnośniki=3563; Koparki kołowe i gąsienicowe=3568; Koparko-ładowarki=3572; Maszyny drogowe=3580; Pozostałe maszyny budowlane=3589; Wózki widłowe=3584; Ładowarki=3576 | Ciężarowe (3559): Autobus=3564; Autolaweta=3569; Chłodnia i izoterma=3573; Ciągnik siodłowy=3577; Dźwigi, żurawie, podnośniki mobilne=3581; Plandeka (firana)=3585; Pojazdy do zabudowy=3590; Pozostałe pojazdy ciężarowe=3597; Wywrotka=3594 | Części samochodowe (82): Dostawcze i Ciężarowe=1401; Osobowe=1399; Pozostałe=1403; Samochody na części=1465 | Dostawcze (3558): Autolaweta=3565; Chłodnia i izoterma=3570; Furgon (blaszak)=3574; Kamper=3578; Kontener=3582; Plandeka=3586; Pozostałe pojazdy dostawcze=3598; Skrzynia=3591; Wywrotka=3595 | Motocykle i Skutery (81): Chopper - Cruiser=1365; Cross=1369; Enduro - Funbike=1367; Motorower=1371; Pozostałe Motocykle i Skutery=1381; Quad - ATV=1373; Skuter=1375; Sportowy=1377; Szosowo - Turystyczny=1379 | Opony i Felgi (658): Felgi=1385; Koła=1387; Opony=1383; Pozostałe Opony i Felgi=1389 | Przyczepy i Naczepy (3561): Naczepy burtowe i plandeki=3562; Naczepy chłodnie i izotermy=3567; Naczepy pozostałe=3575; Naczepy wywrotki=3571; Naczepy z ruchomą podłogą=4936; Przyczepy burtowe i plandeki=3579; Przyczepy do przewozu pojazdów=3588; Przyczepy do samochodów osobowych=3593; Przyczepy kempingowe=3583; Przyczepy pozostałe=3596 | Wyposażenie i akcesoria samochodowe (4482): Apteczki, gaśnice, kamizelki, trójkąty=4483; Bagażniki=4485; Chlapacze=4486; Czujniki i kamery cofania=4487; Dywaniki=4488; Elektronika samochodowa=4489; Emblematy=4490; Foteliki samochodowe=4491; Gadżety motoryzacyjne=4492; Holowanie=4493; Kanistry, lejki i pompki do paliwa=4494; Literatura motoryzacyjna=4495; Off-road=4497; Organizery i siatki do bagażnika=4498; Osłony na szyby=4499; Pióra wycieraczek=4500; Pokrowce=4501; Pozostałe=4509; Prostowniki i kable rozruchowe=4502; Ramki do tablic rejestracyjnych=4503; Skrobaczki do szyb=4504; Tachografy=4505; Tuning wizualny=4496; Uchwyty, wieszaki=4506; Zabezpieczenia=4507; Żarówki=4508

**Marki samochodów osobowych (112).** Marka to podkategoria Samochodów osobowych (`84`); wpisujesz jej ID w `subcategoryId`, a model filtrem `filter_enum_model` (jego wartości podaje `listFilters` z `subcategoryId` marki). `Nazwa=ID; ...`:

Abarth=4876; Acura=4877; Aixam=1556; Alfa Romeo=181; Ariel=5191; Aston Martin=4879; Audi=182; Baic=4880; Bajaj=5578; BAW=5193; Bentley=4881; Blaval=5213; BMW=183; BMW Alpina=4878; Buick=4886; BYD=4870; Cadillac=1353; Changan=5475; Chatenet=4887; Chery=5186; Chery Fulwin=5472; Chevrolet=184; Chrysler=185; Citroen=186; Cupra=4769; Dacia=1347; Daewoo=187; Daihatsu=1349; Denza=5197; DFSK=4888; Dodge=1343; Dongfeng=5575; DR MOTOR=5445; DS Automobiles=4889; e.GO=4890; Elaris=4891; Eletroride=5576; Exeed=5688; Ferrari=4892; Fiat=188; Ford=189; Forthing=4893; GAC=5476; Geely=5187; Genesis=4894; GMC=4895; GWM=5650; Honda=190; Hongqi=4896; Hummer=4897; Hyundai=191; ICH-X=5446; Ineos=4898; Infiniti=1554; Isuzu=4899; Iveco=4900; Jaecoo=4868; Jaguar=1345; Jeep=1221; Jetour=5689; Kia=192; Lada=4901; Lamborghini=4902; Lancia=193; Land Rover=1219; Leapmotor=4952; Lexus=1351; Ligier=4904; Lincoln=4905; Lotus=4953; Lucid=4954; Luqi=5690; Lynk & Co=4955; Maextro=5196; Maserati=4906; Maxus=4907; Mazda=194; McLaren=4909; Melex=5591; Mercedes-Benz=195; MG=4875; Microcar=4933; Mini=1339; Mitsubishi=196; Nissan=197; Omoda=4869; Opel=198; Peugeot=199; Polonez=1257; Pontiac=4934; Porsche=1341; Pozostałe osobowe=209; RAM=4935; Renault=200; Rolls-Royce=4949; Rover=296; Saab=201; Seat=202; Skoda=203; Smart=1337; Sportequipe=5470; SsangYong / KGM=1558; Subaru=204; Suzuki=205; SWM=5214; Tesla=4950; Toyota=206; Volkswagen=207; Volvo=208; XPeng=5474; ZEEKR=5471; Zhidou=4951

ID głębszych poziomów, które nie są tu wypisane (np. lokalizacje i typy w Pracy, drugi poziom Elektroniki, Mody, Domu i Ogrodu, Zwierząt, Sportu), znajdziesz przez `listFilters` z `search` albo w rekordzie `ID_CATALOG`.

#### Co dostajesz (pola datasetu)

`id`, `url`, `title`, `description`, `category`, `categoryId`, `city`, `region`, `priceValue`, `priceCurrency`, `priceLabel`, `negotiable`, `salaryFrom`, `salaryTo`, `salaryCurrency`, `salaryPeriod`, `salaryGross`, `salaryText`, `salarySource`, `salaryConfidence`, `shiftCount`, `shiftLengthHours`, `weekendWork`, `nightWork` (cztery ostatnie i płaca z opisu: patrz [Płaca i zmiany z opisu](#placa-z-opisu)), `sellerName`, `sellerType`, `isPromoted`, `datePosted`, `dateRefreshed`, `validTo`, `photos` (do 10 adresów), `params` (parametry oferty), `externalUrl` (adres, do którego OLX przekierowuje ofertę, jeśli jest), `enriched` (`false`, chyba że oferta została wzbogacona, patrz niżej), `scrapedAt`.
Pole, którego oferta nie ma, ma wartość `null`. Oferty są deduplikowane po `id` w obrębie przebiegu.

#### Opcjonalne wzbogacanie ofert z Otomoto / Otodom (domyślnie wyłączone)

Część ogłoszeń OLX (Motoryzacja, Nieruchomości) jest tylko wizytówką: pełna oferta jest na **Otomoto** albo **Otodom** (pole `externalUrl`). Po włączeniu **„Wzbogać oferty z Otomoto / Otodom”** (`enrichDetails: true`) aktor otwiera takie strony i dodaje:

| Pole | Zawartość |
|---|---|
| `enriched` | `true`, gdy oferta została wzbogacona |
| `enrichmentSource`, `enrichmentStatus` | `otomoto` / `otodom` oraz status: `ok`, `limit_reached`, `stopped_blocked`, `budget_reached`, `not_found`, `parse_error`, `error` (`enrichmentError` opisuje błąd) |
| `equipment`, `equipmentGroups` | wyposażenie (Otomoto: lista i grupy; Otodom: wyposażenie, dodatki, media) |
| `specification` | specyfikacja: marka, model, wersja, rok, przebieg, moc, pojemność... (Otomoto) albo powierzchnia, pokoje, piętro, rok budowy, ogrzewanie... (Otodom) |
| `externalDetails` | cena, sprzedawca, współrzędne (Otomoto) lub cena, czynsz, kaucja, powierzchnia (Otodom) |
| `monthlyCost` | **tylko Otodom, wynajem**: cena najmu + wykryte dodatkowe opłaty miesięczne (czynsz administracyjny, media...), suma `totalMonthly`, kaucja, pozycje i poziom pewności |

Zasady ruchu (regulaminy Otomoto i Otodom zabraniają hurtowej agregacji, dlatego ruch jest celowo mały):

- wzbogacane są wyłącznie oferty OLX z `externalUrl` na `www.otomoto.pl` lub `www.otodom.pl`,
- przerwa co najmniej **2 sekundy** między zapytaniami do tych serwisów,
- limit **`maxEnrichments`** na przebieg: domyślnie 20, maksymalnie 100 (każda oferta przekierowana zużywa jedno miejsce),
- w obrębie przebiegu ten sam adres nie jest pobierany drugi raz,
- po odpowiedzi **403 / 429** wzbogacanie kończy się do końca przebiegu, a zebrane dane zostają (przebieg kończy się sukcesem; pozostałe oferty mają `enrichmentStatus: stopped_blocked`).

**Rozliczenie (pay-per-event).** Gdy aktor ma cennik pay-per-event, używa dwóch zdarzeń: `result` (każda zapisana oferta bazowa) oraz `enriched-listing` (tylko oferta z `enriched: true`, naliczana po zapisie do datasetu). Wzbogacanie jest płatne wyłącznie za oferty faktycznie wzbogacone; błędy, brak strony i oferty pominięte przez limit nie są naliczane. Aktor respektuje limit kosztu przebiegu ustawiony przez użytkownika. Bez cennika wszystko działa tak samo, tylko nic nie jest naliczane.

Przykład:

```json
{
  "category": "motoryzacja",
  "maxItems": 50,
  "enrichDetails": true,
  "maxEnrichments": 10
}
```

#### Tryb listFilters (katalog kategorii i filtrów, bez opłat za oferty)

Ustaw **Tryb pracy** na `listFilters` (`"mode": "listFilters"`), a aktor zamiast ofert zwróci **katalog kategorii i filtrów OLX**: numery podkategorii i marek, klucze do pola `filters` i ich dozwolone wartości. Dane pochodzą z kopii metadanych OLX zapisanej w aktorze: **aktor nie wysyła żadnego zapytania do OLX** i **nie nalicza zdarzeń `result` ani `enriched-listing`** (płatny jest tylko start przebiegu, ok. 0,00005 USD). Sprawdza się go na początku: najpierw `listFilters`, potem zwykły przebieg z poprawnym `subcategoryId` i `filters`.

| Wejście | Wynik |
|---|---|
| samo `"mode": "listFilters"` | lista 19 kategorii głównych z ID, nazwą, liczbą filtrów i informacją, czy aktor je obsługuje; dodatkowo rekordy `ID_CATALOG` i `ID_CATALOG_CSV` w Key-value store ze wszystkimi ID (patrz [Skąd wziąć ID](#skad-wziac-id)) |
| `"search": "toyota"` | trafienia po nazwie (kategoria, podkategoria, marka): ID, ścieżka, gotowy fragment wejścia (`scrapeInput`); dla marki auta także lista modeli |
| `"subcategoryId": 206` | po jednym rekordzie na każdy filtr tego węzła (dla marki Toyota także filtr `filter_enum_model` z listą 46 modeli) |
| `"search": "wynajem"` + `"subcategoryId": 3` | szukanie tylko w poddrzewie wskazanego węzła |

```json
{
  "mode": "listFilters",
  "search": "toyota"
}
```

```json
{
  "mode": "listFilters",
  "subcategoryId": 206
}
```

Rekord filtra ma pola: `categoryId`, `categoryName`, `categoryPath`, `key` (pełny klucz do `filters`), `labelPl`, `type` (`enum`, `bool`, `range` albo `text`), `howToEnter` (jak wpisać), `values` (lista `{value, label}`), `unit`, `hints` (podpowiedzi zakresów z interfejsu OLX) oraz `supported`, `inheritedFromId`, `note`. Kategorię główną można wybrać polem `category` (poza domyślną „praca”) albo `subcategoryId` (np. `4` = Praca). `maxItems` ogranicza liczbę rekordów. Pozostałe pola wejścia (miasto, filtry, proxy, wzbogacanie) są w tym trybie ignorowane. Katalog jest kopią z 2026-10-30; gdy OLX doda nowe filtry, aktor ich nie zna do czasu aktualizacji.

**Rozliczenie.** Rekordy trafiają do datasetu bez nazwy zdarzenia, więc przy cenniku pay-per-event nic za nie nie jest naliczane (cennik aktora nie zawiera syntetycznego zdarzenia `apify-default-dataset-item`, które naliczałoby opłatę za każdy zapis do datasetu).

#### Wynik dla LLM / MCP (agenci AI)

Domyślny format wyniku (`"outputFormat": "full"`) **się nie zmienia**: wszystkie pola, jak dotąd. Dla agentów AI (Apify MCP, własne skrypty z modelami) są dwa krótsze formaty:

| `outputFormat` | Co trafia do datasetu | Rekord `OUTPUT` (Key-value store) |
|---|---|---|
| `full` (domyślnie) | pełny rekord oferty | tylko małe podsumowanie (do 100 KB): liczba ofert, kategoria, link do datasetu, format; bez ofert |
| `compact` | krótki rekord o **stałym kształcie** (te same klucze w każdym rekordzie, brak danych = `null`) | JSON `{summary, offers}` |
| `markdown` | rekord `{id, url, markdown}`: gotowy blok Markdown oferty | tekst Markdown: zdanie podsumowania i bloki ofert |

Rekord `compact` (pola domyślne): `id`, `url`, `title`, `price` (cena **albo płaca jako gotowe zdanie**, np. `32-42 PLN brutto / godz.`), `city`, `seller`, `posted` (RRRR-MM-DD), `params`, `description` (zwykły tekst, bez HTML), `descriptionTruncated`, `schedule` (zmiany i grafik z opisu, np. `2 zmiany po 12 h; praca w weekendy`), `details` (skrót danych z Otomoto/Otodom dla ofert wzbogaconych, inaczej `null`). Zdjęć, ID technicznych i `scrapedAt` w tym formacie nie ma.

| Pole wejścia | Działanie |
|---|---|
| `llmFields` | lista wybranych pól, np. `["title","price","city"]`; `id` i `url` są zawsze. Dostępne: `title`, `price`, `city`, `region`, `seller`, `posted`, `params`, `description`, `schedule`, `details`, `salary`, `photos`, `externalUrl`. Puste = zestaw domyślny |
| `maxDescriptionChars` | długość opisu w znakach (domyślnie 400, `0` = bez opisu); obcięty opis ma `descriptionTruncated: true` |
| `includeSummary` | podsumowanie w `OUTPUT` (domyślnie tak): liczba ofert, czy wynik jest ucięty i dlaczego |
| `tokenBudget` | przybliżony limit tokenów wyniku (1 token = 2,5 znaku). `0` = **automatycznie 8000** (ok. 20 ofert) w `compact` i `markdown`. W `full` nie ma limitu i pole jest ignorowane |

Zbieranie kończy się, gdy budżet jest wyczerpany (`stopReason: token_budget` w `RUN_SUMMARY` i `OUTPUT.summary`): oferta mieści się w całości albo wcale, **zawsze wchodzi co najmniej jedna**, a niezapisane oferty nie są naliczane. `maxItems` działa razem z budżetem: kończy pierwszy z limitów.

```json
{
  "category": "praca",
  "city": "Wrocław",
  "radiusKm": 20,
  "maxItems": 20,
  "outputFormat": "compact"
}
```

**Rozliczenie.** Format nie zmienia cennika: jeden zapisany rekord to jedno zdarzenie `result` (i opcjonalnie `enriched-listing`), tak samo w `full`, `compact` i `markdown`. Zapis `OUTPUT` nie jest zdarzeniem.

**Użycie przez Apify MCP.** `call-actor` zwraca status i identyfikatory magazynów, nie oferty. Oferty czytasz narzędziem `get-dataset-items` (domyślnie 20 rekordów) albo całość jednym odczytem rekordu `OUTPUT` z domyślnego Key-value store (`get-key-value-store-record`). Dla agentów zalecamy `maxItems` 10-20 i `outputFormat: compact`.

**Pomiar rozmiaru** (tokenizer cl100k, 41 prawdziwych ofert pracy z całej Polski, opis 400 znaków): `full` ok. 1170 tokenów na ofertę (JSON bez wcięć; z wcięciami ok. 1280), `compact` ok. 360 (−69% względem `full` bez wcięć), `markdown` ok. 345 (−70%). Wynik zależy od kategorii i długości opisów; inne tokenizery dają inne liczby. Rekord `OUTPUT` w `compact` jest zapisywany z wcięciami, więc zajmuje ok. 15% więcej tokenów niż same rekordy datasetu; agentom polecamy dataset albo `markdown`.

<a id="placa-z-opisu"></a>

#### Płaca i zmiany z opisu (oferty pracy)

W ofertach pracy pracodawca często nie wypełnia pola „Wynagrodzenie”, choć kwota jest w opisie (w naszych danych dotyczyło to ok. 43% ofert pracy). Dla kategorii **Praca** aktor czyta wtedy opis i uzupełnia płacę. Zawsze też szuka w opisie informacji o zmianach. Nic z tego nie ma osobnej ceny: oferta to nadal jedno zdarzenie `result`.

| Pole | Znaczenie |
|---|---|
| `salaryFrom`, `salaryTo`, `salaryPeriod`, `salaryGross`, `salaryCurrency`, `salaryText` | jak dotąd; **z opisu uzupełniane tylko wtedy, gdy OLX nie podał kwoty** (parametr OLX nigdy nie jest nadpisywany) |
| `salarySource` | `param` (kwota z parametru OLX), `opis` (wyliczona z opisu) albo `null` (brak płacy) |
| `salaryConfidence` | `high` albo `medium`; dla `param` zawsze `high`. `medium` oznacza, że okres (godzina/miesiąc) lub kontekst odgadnięto, np. z wysokości kwoty |
| `shiftCount` | liczba zmian z opisu: 1, 2, 3 albo 4 (praca ciągła); `null`, gdy brak informacji albo sprzeczne sygnały |
| `shiftLengthHours` | długość zmiany w godzinach (6-12 albo 24) |
| `weekendWork` | `true` = opis mówi o pracy w weekendy, `false` = mówi, że weekendy są wolne, `null` = brak informacji |
| `nightWork` | `true` = praca w nocy, `false` = opis ją wyklucza, `null` = brak informacji |

Zasady płacy z opisu: kwoty muszą mieścić się w rozsądnym zakresie (np. godzinowo 15-250 zł, miesięcznie 1500-60 000 zł), premie, kaucje, dodatki, czynsz i bony są pomijane, stawki „za sztukę”, „za km” czy „za zajęcia” nie są zapisywane, a przy zapisie „do 6000 zł” wypełniane jest tylko `salaryTo`. W formatach `compact` i `markdown` pole `price` jest gotowym zdaniem, a płaca z opisu ma dopisek, np. `30-35 PLN brutto / godz. (z opisu oferty, pewność: wysoka)`; zmiany trafiają do zdania `schedule`.

**Uwaga o dokładności.** To heurystyka (wzorce w tekście), nie dane od OLX, i **może się mylić**. W naszych sprawdzeniach kwoty z opisu zgadzały się z parametrem OLX w ok. 80% ofert, które mają oba źródła; różnice wynikały głównie z innej jednostki w opisie (stawka godzinowa zamiast miesięcznej) albo kwoty netto zamiast brutto. Informacje o zmianach, weekendach i nocach są mniej pewne (w teście na 80 ofertach: trafność 75-94% i kompletność 47-94%, zależnie od pola). Niejednoznaczny opis daje `null`, nie zgadywaną wartość. Przed decyzją (np. o dojeździe czy ofercie pracy) sprawdź treść ogłoszenia pod adresem `url`. Pole `salarySource` pozwala odróżnić dane OLX od odczytanych z opisu.

#### Zachowanie przy błędach

- Błąd 403 (blokada) albo 429 (za dużo zapytań): aktor kończy łagodnie. Jeśli zdążył zapisać oferty, zostają w datasecie, a przebieg kończy się sukcesem z komunikatem; jeśli nie zapisał żadnej, przebieg kończy się błędem z podpowiedzią o proxy.
- Podsumowanie przebiegu (liczba ofert, stron, powód zakończenia, kod HTTP, użyte proxy) jest w **Key-value store** pod kluczem `RUN_SUMMARY`.

#### Czego aktor nie robi

Nie ma bazy danych, dashboardu ani monitora cyklicznego. Nie pobiera numerów telefonów.

#### Rozwój lokalny

```
python -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt pytest
.venv\Scripts\python.exe -m pytest tests
apify run --input-file input.json
```

### English

<a id="english"></a>

This Actor collects listings from **OLX.pl** (Poland): one category or subcategory (including a car make), optionally one city with a **radius in km**, and **OLX filters (up to 392 different filter keys** across 19 top-level categories: price, year, mileage, fuel, area, contract type, condition and many more). Optionally it **enriches listings with data from Otomoto and Otodom** (equipment, specification, monthly rental cost). Limit: **up to 1000 listings per run**. Results go to the Apify dataset (table, JSON, CSV, Excel).

**Car make and model:** there are no separate "make" and "model" fields. The make is a subcategory (`subcategoryId`, e.g. Toyota = `206`) and the model is the filter `filter_enum_model=corolla`. See [How to set filters step by step](#filters-step-by-step) for details and ready-made examples. The most common filters are listed below; the keys and values of every category are returned by the `listFilters` mode (see below), and category and make IDs are in [Where to get IDs](#where-to-get-ids). A wrong key or value ends the run with a message (in the log and the run status) that lists the valid keys or values for the selected category.

#### Important notes

- Results are for **your own analysis**. **Do not resell the listings** or use them for brokering other people's offers.
- **Per-run item limit**: default 100, maximum 1000. The Actor does not export the whole OLX database, and OLX itself returns at most about 1000 results per query.
- There is a pause of at least 1 second between requests to OLX (at least 2 seconds to Otomoto/Otodom when enrichment is on).
- This is an unofficial tool. It is not affiliated with OLX and uses a publicly reachable, undocumented API that OLX may change or block at any time.
- You are responsible for using the data in line with OLX's terms and applicable law (including personal data rules).

#### How to use

1. Pick a **Category** (for example Praca = jobs).
2. Optionally set **City** and **Radius (km)**. A radius needs a city. Allowed: 0, 2, 5, 10, 15, 20, 30, 50, 75, 100.
3. Set the **Item limit per run** (default 100, max 1000). Optionally turn on **Enrich listings from Otomoto / Otodom** and set the **Enrichment limit per run** (see below).
4. Optionally add **OLX filters** as `key=value`, for example `owner_type=private`, `sort_by=created_at:desc`, `filter_float_price:from=1000`. A wrong key or value stops the run with a message that lists the valid options. Rules and examples: [How to set filters step by step](#filters-step-by-step).
5. **Proxy**: none by default. If OLX answers 403 or 429, try `datacenter` or `residential` (requires access to those proxy groups on your Apify account; residential is billed per GB).
6. Run the Actor and read the results in **Storage → Dataset**.

<a id="filters-step-by-step"></a>

#### How to set filters step by step

1. **Pick the category** in `category` (table below). It is one of 8 top-level categories.
2. **Narrow to a subcategory** to get more filters: put its number in `subcategoryId`. Filters are validated against the **selected** (sub)category, and top-level categories have few of them (e.g. Motors: only price, sort order, seller type). Cars = `84`, Toyota = `206`, Flats for rent = `15`, Wireless headphones = `1973`. Numbers are in [Where to get IDs](#where-to-get-ids) or from `listFilters` mode.
3. **Add filters** in `filters`. Each entry is a separate string `key=value`, e.g. `filter_enum_petrol=diesel`. Values are case-insensitive and can be the code (`diesel`) or the OLX label (`Diesel`). Do not append `[0]` to the key.
4. **Ranges** (price, year, mileage, area) are two separate entries: `key:from=MIN` and `key:to=MAX`. One of them is enough (e.g. only `filter_float_year:from=2015`). Plain numbers, no spaces or units. Integer fields (year, mileage, power) reject decimals.
5. **Several values** of one filter: repeat the key; it works as "OR": `filter_enum_petrol=diesel` + `filter_enum_petrol=petrol` returns diesel and petrol cars. `sort_by` and `owner_type` accept a single value.
6. **Yes/no fields** are written `key=1` (e.g. `filter_bool_remote_recruitment=1`). `=0` is rejected because in OLX it means "only listings without this feature".
7. **City and radius**: `city` (a name such as `Wrocław`; for an ambiguous name add the county: `Bardo, ząbkowicki`) and `radiusKm` (0, 2, 5, 10, 15, 20, 30, 50, 75, 100). A radius needs a city.
8. **Start with a small `maxItems`** (e.g. 20), check the results, then raise it up to 1000. A wrong key or value ends the run immediately with a list of valid options and costs nothing.

**Car make and model.** No separate fields. Make = the make's `subcategoryId` (e.g. Toyota `206`; the list of makes with numbers is in [Where to get IDs](#where-to-get-ids)), model = `filter_enum_model=<code>` (e.g. `corolla`). The model filter works only together with a make's `subcategoryId`; with `subcategoryId: 84` (all makes) it is rejected. The other car filters (fuel, year, mileage...) work for both `84` and a make.

**Salary in Jobs.** OLX has no "salary from-to" filter in Jobs. Salary comes in the fields `salaryFrom`, `salaryTo`, `salaryText` (when the listing states it), and you can sort by it: `sort_by=filter_float_salary_to:desc`.

##### Top-level categories

| `category` | Category | OLX ID |
|---|---|---|
| `praca` | Jobs | 4 |
| `motoryzacja` | Motors | 5 |
| `nieruchomosci` | Real estate | 3 |
| `elektronika` | Electronics | 99 |
| `dom-ogrod` | Home & Garden | 628 |
| `moda` | Fashion | 87 |
| `zwierzeta` | Animals | 103 |
| `sport-hobby` | Sports & Hobby | 767 |

OLX has 19 top-level categories. The others (e.g. Kids 88, Books & Music 751, Agriculture 757, Health & Beauty 3647, Business & Industry 4269, Services 4371, Construction 5216) are not available in this Actor yet.

##### Most common filters

Enter keys in `filters` as `key=value` (ranges: `key:from=` / `key:to=`). Availability depends on the (sub)category.

**Almost everywhere:** `sort_by` (`created_at:desc` newest, `relevance:desc` default, `filter_float_price:asc` / `:desc` by price), `owner_type` (`private`, `business`), `filter_float_price` (range, PLN).

**Motors > Cars (`subcategoryId: 84`) and makes (e.g. `206`):** `filter_float_year`, `filter_float_milage` (km), `filter_float_enginesize` (cm³), `filter_float_enginepower` (hp) are integer ranges. Lists: `filter_enum_petrol` (`petrol`, `diesel`, `lpg`, `cng` = CNG and hybrid, `plugin-hybrid`, `electric`), `filter_enum_transmission` (`manual`, `automatic`), `filter_enum_car_body` (`sedan`, `hatchback`, `estate-car`, `suv`, `coupe`, `cabriolet`, `minibus`, `mvp`, `pickup`, `off-road-vehicle`), `filter_enum_condition` (`notdamaged`, `damaged`), `filter_enum_drive` (`front-wheel`, `rear-wheel`, `all-wheel-permanent`, `all-wheel-auto`, `all-wheel-lock`), `filter_enum_color` (`bialy`, `czarny`, `szary`, `srebrny`...). `filter_enum_model` (e.g. Toyota: `corolla`, `yaris`, `auris`) only with a make's `subcategoryId`.

**Real estate > Flats > Rent (`15`) / Sale (`14`):** `filter_float_price` and `filter_float_m` (area, m²) ranges; lists `filter_enum_rooms` (`one`, `two`, `three`, `four` = 4+), `filter_enum_furniture` (`yes`, `no`), `filter_enum_floor_select` (`floor_0` = ground floor, `floor_1`...), `filter_enum_builttype` (`blok`, `kamienica`, `apartamentowiec`, `loft`...); sale only: `filter_enum_market` (`primary`, `secondary`); rent only: `filter_enum_pets`, `filter_enum_winda` (`Tak`/`Nie`), `filter_enum_parking`.

**Jobs (`category: praca`):** `filter_enum_type` (`fulltime`, `parttime`, `halftime`, `seasonal`, `b2b`), `filter_enum_agreement` (`part` = employment contract, `zlecenie`, `contract`, `selfemployment`, `practice`, `other`), `filter_enum_workplace` (`on_site`, `remote_work_possibility`, `hybrid`, `traveling`), `filter_enum_experience` (`exp_yes`, `exp_no`), `filter_enum_availability` (`shift_work`, `weekends_work`, `flexible_work`), `filter_enum_special_requirements`, `filter_bool_remote_recruitment` (`=1`), `sort_by` (including `filter_float_salary_to:desc`).

**Electronics:** the set depends on the subcategory. Wireless headphones (`1973`): `filter_enum_state` (`new`, `used`, `damaged`), `filter_enum_brand` (`apple`, `jbl`, `bose`, `sony`...), `filter_enum_type` (`in_ear`, `in_canal`, `on_ear`...), `filter_enum_etui` (`yes`/`no`), `filter_float_price`. Smartphones (`1839`): `filter_enum_state` (`new`, `refurbished`, `used`, `damaged`), `filter_enum_builtinmemory_phones`, `filter_enum_rammemory_phones`, `filter_enum_coloriphone`, `filter_enum_sim_options`. Laptops (`1199`): `filter_enum_state`, `filter_enum_ramsize_laptops`, `filter_enum_disksize_laptops`, `filter_enum_operatingsystem_laptops`, `filter_enum_processorseries_laptops`.

##### Ready-made input examples

**1. Toyota Corolla, diesel, 2015 or newer, up to 150,000 km, newest first:**

```json
{
  "category": "motoryzacja",
  "subcategoryId": 206,
  "maxItems": 100,
  "filters": [
    "filter_enum_model=corolla",
    "filter_enum_petrol=diesel",
    "filter_float_year:from=2015",
    "filter_float_milage:to=150000",
    "sort_by=created_at:desc"
  ]
}
```

**2. Jobs in Wrocław and 20 km around, full-time, employment contract, highest salary first:**

```json
{
  "category": "praca",
  "city": "Wrocław",
  "radiusKm": 20,
  "maxItems": 200,
  "filters": [
    "filter_enum_type=fulltime",
    "filter_enum_agreement=part",
    "sort_by=filter_float_salary_to:desc"
  ]
}
```

**3. Flats to rent in Wrocław: 2,000-3,500 PLN, 40-70 m², 2 or 3 rooms, furnished:**

```json
{
  "category": "nieruchomosci",
  "subcategoryId": 15,
  "city": "Wrocław",
  "radiusKm": 5,
  "maxItems": 100,
  "filters": [
    "filter_float_price:from=2000",
    "filter_float_price:to=3500",
    "filter_float_m:from=40",
    "filter_float_m:to=70",
    "filter_enum_rooms=two",
    "filter_enum_rooms=three",
    "filter_enum_furniture=yes",
    "owner_type=private"
  ]
}
```

**4. Wireless headphones, new, up to 400 PLN:**

```json
{
  "category": "elektronika",
  "subcategoryId": 1973,
  "maxItems": 100,
  "filters": [
    "filter_enum_state=new",
    "filter_float_price:to=400",
    "sort_by=filter_float_price:asc"
  ]
}
```

**5. Cars (all makes) from private sellers, 2012+, automatic, petrol or diesel, enriched with Otomoto data:**

```json
{
  "category": "motoryzacja",
  "subcategoryId": 84,
  "maxItems": 50,
  "enrichDetails": true,
  "maxEnrichments": 20,
  "filters": [
    "owner_type=private",
    "filter_float_year:from=2012",
    "filter_enum_transmission=automatic",
    "filter_enum_petrol=petrol",
    "filter_enum_petrol=diesel"
  ]
}
```

**6. Remote jobs, no experience required, online recruitment, newest first:**

```json
{
  "category": "praca",
  "maxItems": 100,
  "filters": [
    "filter_enum_workplace=remote_work_possibility",
    "filter_enum_experience=exp_no",
    "filter_bool_remote_recruitment=1",
    "sort_by=created_at:desc"
  ]
}
```

<a id="where-to-get-ids"></a>

#### Where to get IDs (categories, subcategories, brands)

You put the number (ID) in the `subcategoryId` field. There are three ways to find it without looking at the Actor's code:

| Way | How |
|---|---|
| 1. The lists | the 19 top-level categories below; first-level subcategories, selected deeper levels and all passenger-car makes in the Polish section [Skąd wziąć ID](#skad-wziac-id) above |
| 2. `listFilters` mode (no per-listing charges) | `{"mode": "listFilters"}` returns the category list with IDs. `{"mode": "listFilters", "search": "toyota"}` returns name matches with ID and path (Toyota = `206`); `"search": "wynajem"` (Polish for "rent") shows every "Wynajem" node with its ID. `{"mode": "listFilters", "subcategoryId": 84}` shows the filters of a node |
| 3. The full catalog in one run | a run with `{"mode": "listFilters"}` (no `search`, no `subcategoryId`) also stores the records `ID_CATALOG` (JSON) and `ID_CATALOG_CSV` (CSV for Excel) in the run's **Key-value store**, with **all 3318 nodes** of the OLX tree: `id`, `name`, `parentId`, `path`, `mainCategory`, `isBrand`. Download them in Console (Storage → Key-value store) or via the API (`.../key-value-stores/<id>/records/ID_CATALOG_CSV`) |

Every listing in the results also has a `categoryId` field: the ID of that listing's lowest-level category, which you can use as `subcategoryId`. The `-CID<number>-` part of a listing's `url` is the ID of the **top-level category** (checked on Jobs and Real estate listings), so it does not give you a subcategory or brand ID.

**Top-level categories (19).** The Actor's `category` field supports 8 of them; the rest can be inspected with `listFilters` but not scraped. Names are OLX's Polish names.

| ID | Category (Polish name) | `category` in the Actor |
|---|---|---|
| 3 | Nieruchomości | `nieruchomosci` |
| 4 | Praca | `praca` |
| 5 | Motoryzacja | `motoryzacja` |
| 87 | Moda | `moda` |
| 99 | Elektronika | `elektronika` |
| 103 | Zwierzęta | `zwierzeta` |
| 628 | Dom i Ogród | `dom-ogrod` |
| 767 | Sport i Hobby | `sport-hobby` |
| 88 | Dla Dzieci | no (listFilters only) |
| 751 | Książki i Muzyka | no (listFilters only) |
| 757 | Rolnictwo | no (listFilters only) |
| 1151 | Oddam za darmo | no (listFilters only) |
| 1816 | Noclegi | no (listFilters only) |
| 3018 | Wypożyczalnia | no (listFilters only) |
| 3647 | Zdrowie i Uroda | no (listFilters only) |
| 4042 | Antyki i Kolekcje | no (listFilters only) |
| 4269 | Firma i Przemysł | no (listFilters only) |
| 4371 | Usługi | no (listFilters only) |
| 5216 | Budowa i Remont | no (listFilters only) |

**First-level subcategories, selected deeper levels and all 112 passenger-car makes** are listed as `Name=ID; ...` in the Polish section [Skąd wziąć ID](#skad-wziac-id) above. IDs are numbers and the names are OLX's Polish names (Mieszkania = flats, Wynajem = rent, Sprzedaż = sale, Samochody osobowe = passenger cars). Quick examples: Mieszkania > Wynajem = `15`, Mieszkania > Sprzedaż = `14`, Samochody osobowe = `84`, Toyota = `206`. Anything not listed there is available through `listFilters` with `search` or in `ID_CATALOG`.

#### Optional enrichment from Otomoto / Otodom (off by default)

Some OLX listings (Motors, Real estate) are only a stub: the full advert lives on **Otomoto** or **Otodom** (field `externalUrl`). With **"Enrich listings from Otomoto / Otodom"** (`enrichDetails: true`) the Actor opens those pages and adds:

- `enriched` (`true` when the listing was enriched), `enrichmentSource` (`otomoto` / `otodom`), `enrichmentStatus` (`ok`, `limit_reached`, `stopped_blocked`, `budget_reached`, `not_found`, `parse_error`, `error`) and `enrichmentError`,
- `equipment` and `equipmentGroups` (equipment list; for Otodom: equipment, extras and utilities),
- `specification` (make, model, year, mileage, power... for Otomoto; area, rooms, floor, build year, heating... for Otodom) and `externalDetails` (price, seller, coordinates / rent, deposit, area),
- `monthlyCost` (**Otodom rentals only**): rent plus detected extra monthly charges (admin rent, utilities...), `totalMonthly`, deposit, line items and a confidence level.

Traffic rules (Otomoto and Otodom forbid bulk aggregation, so the traffic is deliberately small):

- only OLX listings whose `externalUrl` points to `www.otomoto.pl` or `www.otodom.pl` are enriched,
- at least **2 seconds** between requests to those sites,
- a per-run cap, **`maxEnrichments`**: default 20, maximum 100 (every redirected listing uses one slot),
- the same URL is never fetched twice within a run,
- on **HTTP 403 / 429** enrichment stops for the rest of the run and everything collected so far is kept (the run still succeeds; the remaining listings get `enrichmentStatus: stopped_blocked`).

**Billing (pay-per-event).** When the Actor has pay-per-event pricing it uses two events: `result` (every base listing saved) and `enriched-listing` (only listings with `enriched: true`, charged after the dataset write). You pay for enrichment only for listings that were actually enriched; failures, missing pages and listings skipped by the cap are not charged. The Actor respects the per-run spending limit you set. Without pricing everything works the same and nothing is charged.

Example:

```json
{
  "category": "motoryzacja",
  "maxItems": 50,
  "enrichDetails": true,
  "maxEnrichments": 10
}
```

#### listFilters mode (category and filter catalog, no per-listing charges)

Set **Mode** to `listFilters` (`"mode": "listFilters"`) and the Actor returns the **OLX category and filter catalog** instead of listings: subcategory and brand IDs, keys for the `filters` field and their allowed values. The data comes from a copy of the OLX metadata bundled with the Actor: **it sends no request to OLX** and **does not charge the `result` or `enriched-listing` events** (only the run start, about 0.00005 USD, is billed). Use it first: run `listFilters`, then a normal run with the right `subcategoryId` and `filters`.

| Input | Result |
|---|---|
| just `"mode": "listFilters"` | list of the 19 top-level categories with ID, name, filter count and whether the Actor supports them; also the Key-value store records `ID_CATALOG` and `ID_CATALOG_CSV` with all IDs (see [Where to get IDs](#where-to-get-ids)) |
| `"search": "toyota"` | name matches (category, subcategory, brand): ID, path, a ready input fragment (`scrapeInput`); for a car make also its model list |
| `"subcategoryId": 206` | one record per filter of that node (for Toyota also `filter_enum_model` with 46 models) |
| `"search": "rent"` + `"subcategoryId": 3` | search only inside the subtree of that node (names are Polish, so `wynajem` finds rentals) |

```json
{
  "mode": "listFilters",
  "search": "toyota"
}
```

A filter record has: `categoryId`, `categoryName`, `categoryPath`, `key` (full key for `filters`), `labelPl` (Polish label), `type` (`enum`, `bool`, `range` or `text`), `howToEnter`, `values` (`{value, label}` list), `unit`, `hints` (range suggestions from the OLX UI), plus `supported`, `inheritedFromId`, `note`. Pick a top-level category with `category` (other than the default "praca") or with `subcategoryId` (e.g. `4` = jobs). `maxItems` caps the number of records. Other input fields (city, filters, proxy, enrichment) are ignored in this mode. The catalog is a copy from 2026-10-30.

**Billing.** Records are pushed without an event name, so nothing is charged for them under pay-per-event pricing (the Actor's pricing has no synthetic `apify-default-dataset-item` event, which would charge every dataset write).

#### Output for LLMs / MCP (AI agents)

The default output format (`"outputFormat": "full"`) **is unchanged**: every field, as before. For AI agents (Apify MCP, your own scripts with models) there are two shorter formats:

| `outputFormat` | Dataset content | `OUTPUT` record (Key-value store) |
|---|---|---|
| `full` (default) | full listing record | none |
| `compact` | short record with a **fixed shape** (same keys in every record, missing data = `null`) | JSON `{summary, offers}` |
| `markdown` | record `{id, url, markdown}`: a ready Markdown block per listing | Markdown text: summary sentence plus listing blocks |

`compact` record (default fields): `id`, `url`, `title`, `price` (price **or salary as a ready sentence**, e.g. `32-42 PLN brutto / godz.`), `city`, `seller`, `posted` (YYYY-MM-DD), `params`, `description` (plain text, no HTML), `descriptionTruncated`, `schedule` (shifts and schedule from the description), `details` (short Otomoto/Otodom data for enriched listings, else `null`). Photos, technical IDs and `scrapedAt` are not in this format.

| Input field | Effect |
|---|---|
| `llmFields` | chosen fields, e.g. `["title","price","city"]`; `id` and `url` are always there. Allowed: `title`, `price`, `city`, `region`, `seller`, `posted`, `params`, `description`, `schedule`, `details`, `salary`, `photos`, `externalUrl`. Empty = default set |
| `maxDescriptionChars` | description length in characters (default 400, `0` = no description); a truncated description has `descriptionTruncated: true` |
| `includeSummary` | summary in `OUTPUT` (default on): number of listings, whether the result is cut and why |
| `tokenBudget` | approximate token limit of the result (1 token = 2.5 characters). `0` = **automatic 8000** (about 20 listings) in `compact` and `markdown`. Ignored in `full` (no limit) |

Collection stops when the budget is used up (`stopReason: token_budget` in `RUN_SUMMARY` and `OUTPUT.summary`): a listing fits whole or not at all, **at least one listing is always returned**, and listings that were not saved are not charged. `maxItems` works together with the budget: whichever limit comes first ends the run.

```json
{
  "category": "praca",
  "city": "Wrocław",
  "radiusKm": 20,
  "maxItems": 20,
  "outputFormat": "compact"
}
```

**Billing.** The format does not change pricing: one saved record is one `result` event (and optionally `enriched-listing`), the same in `full`, `compact` and `markdown`. Writing `OUTPUT` is not an event.

**Using it through Apify MCP.** `call-actor` returns the run status and storage IDs, not listings. Read the listings with `get-dataset-items` (20 records by default) or the whole result in one read of the `OUTPUT` record in the default key-value store (`get-key-value-store-record`). For agents we recommend `maxItems` 10-20 and `outputFormat: compact`.

**Size measurement** (cl100k tokenizer, 41 real job listings from all over Poland, description 400 characters): `full` about 1170 tokens per listing (minified JSON; about 1280 with indentation), `compact` about 360 (−69% vs minified `full`), `markdown` about 345 (−70%). It depends on the category and description length; other tokenizers give other numbers. The `compact` `OUTPUT` record is stored with indentation, so it takes about 15% more tokens than the dataset records alone; for agents we recommend the dataset or `markdown`.

#### Salary and shifts from the description (job listings)

In job listings the employer often leaves the "Salary" field empty although the amount is in the description (about 43% of job listings in our data). For the **Praca** (jobs) category the Actor then reads the description and fills the salary in. It also always looks for shift information. None of this has a separate price: a listing is still one `result` event.

| Field | Meaning |
|---|---|
| `salaryFrom`, `salaryTo`, `salaryPeriod`, `salaryGross`, `salaryCurrency`, `salaryText` | as before; **filled from the description only when OLX gave no amount** (the OLX parameter is never overwritten) |
| `salarySource` | `param` (amount from the OLX parameter), `opis` (derived from the description; Polish for "description") or `null` (no salary) |
| `salaryConfidence` | `high` or `medium`; always `high` for `param`. `medium` means the period (hour/month) or context was inferred, e.g. from the size of the amount |
| `shiftCount` | number of shifts from the description: 1, 2, 3 or 4 (continuous work); `null` if there is no information or the signals conflict |
| `shiftLengthHours` | shift length in hours (6-12 or 24) |
| `weekendWork` | `true` = the description mentions weekend work, `false` = it says weekends are free, `null` = no information |
| `nightWork` | `true` = night work, `false` = the description rules it out, `null` = no information |

Rules for the salary from the description: amounts must be in a plausible range (e.g. hourly 15-250 PLN, monthly 1500-60,000 PLN), bonuses, deposits, allowances, rent and vouchers are skipped, per-piece / per-km / per-class rates are not stored, and for "up to 6000 PLN" only `salaryTo` is set. In `compact` and `markdown` the `price` field is a ready sentence and a salary taken from the description carries a note, e.g. `30-35 PLN brutto / godz. (z opisu oferty, pewność: wysoka)`; shift data goes into the `schedule` sentence.

**Accuracy warning.** This is a heuristic (text patterns), not data from OLX, and **it can be wrong**. In our checks the amounts from the description matched the OLX parameter in about 80% of the listings that have both; differences came mostly from a different unit in the text (hourly rate instead of monthly) or net instead of gross. Shift, weekend and night information is less reliable (on 80 listings: precision 75-94% and recall 47-94%, depending on the field). An ambiguous description gives `null`, not a guessed value. Check the listing text at `url` before acting on it. `salarySource` tells OLX data apart from values read from the description.

#### Behaviour on errors

On HTTP 403 or 429 the Actor stops gracefully. Listings already saved stay in the dataset and the run ends as succeeded with a status message; if nothing was saved, the run fails with a hint about proxies. A run summary is stored in the key-value store under `RUN_SUMMARY`.

#### What it does not do

No database, no dashboard, no recurring monitor, no phone numbers.

# Actor input Schema

## `category` (type: `string`):

Kategoria główna OLX.pl. Zestaw dostępnych filtrów zależy od kategorii i podkategorii.

## `subcategoryId` (type: `integer`):

Opcjonalnie. Numer podkategorii OLX z wybranej kategorii. Przykłady: 84 = Samochody osobowe, 206 = marka Toyota (wtedy model podajesz filtrem filter_enum_model=corolla), 15 = Mieszkania na wynajem, 14 = Mieszkania na sprzedaż, 1973 = Słuchawki bezprzewodowe. Podkategoria ma własne, dłuższe filtry niż kategoria główna. Zostaw puste, aby pobierać z całej kategorii.

## `city` (type: `string`):

Opcjonalnie. Nazwa miejscowości, np. Wrocław. Gdy nazwa jest niejednoznaczna, dopisz powiat po przecinku (np. Bardo, ząbkowicki) albo podaj numer ID miejscowości OLX. Formularz w Console jest wstępnie wypełniony wartością Wrocław (próbny przebieg); wyczyść pole, aby szukać w całej Polsce.

## `radiusKm` (type: `integer`):

Opcjonalnie, wymaga podania miasta. Promień wokół miasta w km. Dozwolone wartości: 0, 2, 5, 10, 15, 20, 30, 50, 75, 100.

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

Ile ofert zapisać w jednym przebiegu. Domyślnie 100 (gdy pole pominięto w wywołaniu API), maksymalnie 1000 (tyle najwięcej oddaje OLX na jedno zapytanie). Formularz w Console jest wstępnie wypełniony wartością 20 (tani próbny przebieg).

## `filters` (type: `array`):

Opcjonalnie, do 392 kluczy OLX; każdy wpis to osobny tekst klucz=wartość. Przykłady: filter_enum_petrol=diesel | filter_enum_model=corolla (tylko z podkategorią marki) | owner_type=private | sort_by=created_at:desc | filter_bool_remote_recruitment=1. Zakres to dwa wpisy: filter_float_price:from=1000 i filter_float_price:to=5000. Kilka wartości: powtórz klucz (LUB). Bez \[0]. Zły klucz kończy przebieg bez kosztów. Lista kluczy i wartości: mode=listFilters albo README.

## `proxyType` (type: `string`):

Domyślnie brak proxy. Datacenter i residential wymagają dostępu do tych grup proxy na koncie Apify (residential jest płatne za GB).

## `enrichDetails` (type: `boolean`):

Domyślnie wyłączone. Dla ofert OLX, które przekierowują do Otomoto lub Otodom, dołącza wyposażenie, specyfikację i (Otodom, wynajem) koszt miesięczny z dodatkowymi opłatami. Każda faktycznie wzbogacona oferta jest rozliczana osobnym zdarzeniem „enriched-listing”. Ruch jest ograniczony: przerwa min. 2 s między zapytaniami, limit „maxEnrichments” na przebieg; po odpowiedzi 403/429 wzbogacanie kończy się, a zebrane dane zostają.

## `maxEnrichments` (type: `integer`):

Działa tylko przy włączonym „Wzbogać oferty”. Ile ofert wzbogacić w jednym przebiegu (każda oferta przekierowana do Otomoto/Otodom zużywa jedno miejsce). Domyślnie 20, maksymalnie 100.

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

scrape (domyślnie) = pobiera oferty z OLX. listFilters = zwraca katalog kategorii i filtrów OLX (ID, klucze do pola filters, dozwolone wartości, marki i modele) z lokalnych danych: bez zapytań do OLX i bez zdarzeń result, opłaty tylko za start przebiegu. Bez search i subcategoryId zwraca listę kategorii i zapisuje pełny katalog ID (rekord ID_CATALOG). Z subcategoryId zwraca filtry tej podkategorii lub marki. (EN: listFilters = filter catalog, no OLX requests.)

## `search` (type: `string`):

Tylko mode=listFilters. Nazwa kategorii, podkategorii lub marki do znalezienia, np. toyota, wynajem, laptopy (bez znaczenia wielkość liter i polskie znaki). Zwraca ID, ścieżkę i gotowy fragment wejścia (scrapeInput); dla marki auta także modele. Z subcategoryId szuka tylko w jego poddrzewie. Maksymalnie maxItems trafień.

## `outputFormat` (type: `string`):

full = wszystkie pola oferty (domyślnie, bez zmian). compact = krótki rekord o stałym kształcie dla agentów AI: id, url, tytuł, cena lub płaca jako zdanie, miasto, parametry, opis jako tekst. markdown = gotowy blok Markdown na ofertę (id, url, markdown). W compact i markdown cały wynik trafia też do rekordu OUTPUT (Key-Value) i działa budżet tokenów (domyślnie 8000, ok. 20 ofert).

## `llmFields` (type: `array`):

Opcjonalnie. Lista pól dla compact i markdown, np. \["title","price","city"]. Puste = zestaw domyślny: title, price, city, seller, posted, params, description, schedule, details. Pola id i url są zawsze. Dozwolone: title, price, city, region, seller, posted, params, description, schedule, details, salary, photos, externalUrl.

## `maxDescriptionChars` (type: `integer`):

Tryby compact i markdown: opis oferty jako zwykły tekst (bez HTML), obcięty do tylu znaków na granicy słowa. Domyślnie 400, 0 = bez opisu. Gdy opis obcięto, rekord compact ma descriptionTruncated=true. Nie dotyczy trybu full.

## `includeSummary` (type: `boolean`):

Tryby compact i markdown: dołącz do rekordu OUTPUT podsumowanie na początku (ile ofert, kategoria, miasto, czy wynik jest ucięty i dlaczego, jak go zawęzić). Domyślnie włączone. Dataset zawiera same oferty.

## `tokenBudget` (type: `integer`):

Tryby compact i markdown: przybliżony limit tokenów wyniku (szacunek: 1 token = 2,5 znaku). Zbieranie kończy się po wyczerpaniu budżetu (stopReason=token_budget), niezapisane oferty nie są rozliczane. 0 = automatycznie 8000. Zawsze wchodzi co najmniej jedna oferta. Nie dotyczy trybu full (bez limitu). Maks. 100000.

## Actor input object example

```json
{
  "category": "praca",
  "city": "Wrocław",
  "radiusKm": 20,
  "maxItems": 20,
  "filters": [],
  "proxyType": "none",
  "enrichDetails": false,
  "maxEnrichments": 20,
  "mode": "scrape",
  "search": "toyota",
  "outputFormat": "full",
  "llmFields": [],
  "maxDescriptionChars": 400,
  "includeSummary": true,
  "tokenBudget": 0
}
```

# Actor output Schema

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

Rekordy domyślnego datasetu: oferty OLX (tryb scrape) albo kategorie, trafienia i filtry (tryb listFilters). Widoki w Console: overview, salaryShifts, enriched, llm, filters, categories.

## `output` (type: `string`):

Rekord OUTPUT w Key-value store. Format compact (JSON) lub markdown (tekst): podsumowanie i wszystkie oferty, z budżetem tokenów. Format full (domyślny): tylko małe podsumowanie przebiegu (liczba ofert, kategoria, link do datasetu), oferty są w datasecie.

## `runSummary` (type: `string`):

Rekord RUN_SUMMARY: tryb, liczba zapisanych rekordów, powód zakończenia, liczba zapytań do OLX, informacje o rozliczeniu.

# 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 = {
    "category": "praca",
    "city": "Wrocław",
    "radiusKm": 20,
    "maxItems": 20,
    "filters": [],
    "search": "toyota"
};

// Run the Actor and wait for it to finish
const run = await client.actor("compelling_queue_klo/olx-poland-scraper").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 = {
    "category": "praca",
    "city": "Wrocław",
    "radiusKm": 20,
    "maxItems": 20,
    "filters": [],
    "search": "toyota",
}

# Run the Actor and wait for it to finish
run = client.actor("compelling_queue_klo/olx-poland-scraper").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 '{
  "category": "praca",
  "city": "Wrocław",
  "radiusKm": 20,
  "maxItems": 20,
  "filters": [],
  "search": "toyota"
}' |
apify call compelling_queue_klo/olx-poland-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,compelling_queue_klo/olx-poland-scraper"
        }
    }
}
```

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/hcZO0yj6FAu9bgW8I/builds/F65KDxOBbXTHA0vGU/openapi.json
