# OuedKniss Multi Scraper (`hamza325/ouedkniss-multi-scraper`) Actor

Scrape ouedkniss.com, Algeria's largest classifieds marketplace. Five modes cover every common workflow: search by keyword, browse a category, walk the full category menu, fetch a hand-picked list of listing URLs, or pull the sponsored-listing banner the site shows at the top of every page.

- **URL**: https://apify.com/hamza325/ouedkniss-multi-scraper.md
- **Developed by:** [Hamza Abbad](https://apify.com/hamza325) (community)
- **Categories:**
- **Stats:** 1 total users, 1 monthly users, 85.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 results

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OuedKniss Multi-Scraper

Scrape ouedkniss.com — Algeria's largest classifieds marketplace — directly through the site's own API. Every record is a normalized `Listing` ready to feed straight into a spreadsheet, a database, or another Actor downstream.

### What you can scrape

The Actor drives seven modes from a single `What to scrape` input. Pick the one that matches what you want.

| Mode | What it returns | Best for |
| --- | --- | --- |
| `search` — Search by keyword | Up to N listings matching a free-text query, paginated and optionally filtered | Market research, price tracking, lead lists |
| `category` — Browse a category | All listings in one category, paginated (same engine path as search) | Browsing a niche (cars, phones, real estate…) |
| `directUrls` — Specific listing URLs | A specific list of listing pages, fully enriched | Resuming a previous run, following up on alerts |
| `categories` — Category menu (full tree) | The full menu tree (top-level + recursive sub-trees) | Building a category→id map, discovering wilaya IDs |
| `topAnnounList` — Sponsored listings (banner) | The paid cross-listing banner — different response shape (store metadata only). Scope with `Category page URL` to get the category-specific banner instead of the site-wide one. | Tracking which listings the site is promoting in a given category |
| `seller` — Seller listings | Every listing one seller has published, paginated — works the same for business stores (`/store/…` URLs) and personal accounts (`/membre/…` URLs) | Tracking a shop's inventory, monitoring a seller's listings, building seller histories |
| `similar` — Similar products | Products algorithmically similar to each listing URL you provide | Discovering alternatives, competitive research |

> The `topAnnounList` mode produces banner rows. The `categories` mode produces category-tree rows. The `seller` mode produces organic-shaped listing rows. The `similar` mode produces organic-shaped listing rows tagged with `attributes.slot = "similar"`. All modes share the same flat row shape; each mode populates only the fields it has data for (the others are null or empty).

> **The banner is heavily category-dependent.** The top-30 banner entries for the cars category (`/automobiles_vehicules`) and real estate (`/immobilier`) share **0 items** with each other, and only 2/30 overlap with the unfiltered cross-listing banner. Always pass a category URL to get a meaningful banner for a niche.

### Why this Actor

- **Fast and lightweight.** Direct API requests. Fast on cold start, gentle on memory, cheap to run at scale.
- **Flat, normalized output.** Every record is a `Listing` with no nested objects (except the free-form specs map). Same shape every run, ready to feed into a spreadsheet, a database, or another Actor downstream.
- **Contact info that actually works.** When `Include real phone + email` is on, the Actor fetches contact data in the one mode that carries it: **Specific listing URLs** reveals the real phone numbers and emails the site gates behind a click on each listing. The toggle does nothing in the other modes — `search`, `category`, `categories`, `topAnnounList`, and `seller` runs always produce empty phone/email arrays regardless.
- **You only ever paste URLs.** Every reference field takes a full page URL, straight from the browser's address bar: a category page into `Category page URL`, a seller's store/profile page into `Seller page URL`, listing pages into `Listing URLs`. No IDs, no slugs, no values copied out of the address bar — and the Actor tells you exactly what it expected if a URL points at the wrong kind of page.

### Quick start

1. Open this Actor in Apify Console.

2. Click **Try actor** and set at minimum:

   ```json
   { "What to scrape": "Search by keyword", "searchQuery": "iphone" }
   ```

3. Click **Start**. Results land in the **Dataset** tab, one JSON object per listing. If you ran **Category menu**, switch the dataset view to **Category menu** so the rows render meaningfully.

The smallest valid input runs a keyword search across the whole site, with sensible defaults for everything else.

### Tutorial: a real workflow

#### 1. Find a niche you care about

Pick a category. The easiest way to discover them is to first run the Actor in **Category menu** mode:

```json
{ "What to scrape": "Category menu (full tree)", "maxDepth": 3 }
```

The dataset will contain one record per category node. Look at the `Slug` column of the leaf you want — for example `automobiles_vehicules` — and build the category page URL from it: `https://www.ouedkniss.com/automobiles_vehicules/1`.

#### 2. Scrape that category, filtered

```json
{
  "What to scrape": "Browse a category",
  "Category page URL": "https://www.ouedkniss.com/automobiles_vehicules/1",
  "Filter by wilaya (region)": ["16"],
  "Only listings with a price": true,
  "Maximum results": 500,
  "Maximum pages": 20
}
```

This walks up to 20 pages of the cars category, filtered to Algiers (`16`), skipping listings without a price. Roughly 960 listings.

#### 3. Enrich a hand-picked set

Take the listing IDs you want from the previous dataset and re-run with **Specific listing URLs** + contact info on:

```json
{
  "What to scrape": "Specific listing URLs",
  "Listing URLs": [
    "https://www.ouedkniss.com/renault-clio-d57354225",
    "https://www.ouedkniss.com/peugeot-208-d59321100"
  ],
  "Include real phone + email": true
}
```

Each listing is returned fully enriched: real phone numbers (with WhatsApp / Viber / Telegram flags), real email, gallery images, seller profile URL. Switch the dataset view to **Listing details** to see all the enriched fields at once.

#### 4. Check the sponsored banner

```json
{ "What to scrape": "Sponsored listings (banner)", "Maximum results": 30 }
```

Returns whatever the site is currently promoting in the site-wide cross-listing banner. Banner rows carry store metadata and one image, so they don't get confused with organic rows if you ever union this dataset with another Actor's output.

To get the banner **scoped to a specific category** (which is what you usually want — the site-wide banner is a mix of every category), pass the category page URL:

```json
{ "What to scrape": "Sponsored listings (banner)", "Category page URL": "https://www.ouedkniss.com/automobiles_vehicules/1", "Maximum results": 30 }
```

#### 5. Track a seller's inventory

Pick any store page on the site (or from `sellerProfileUrl` on a listing row) and paste it into `Seller`:

```json
{
  "What to scrape": "Seller listings",
  "Seller page URL": "https://www.ouedkniss.com/store/290/dyalkom/",
  "Include real phone + email": true
}
```

You get the seller's full listing grid — every row is one of their listings. The exact same flow works for personal accounts: paste a `/membre/…` URL and each listing row comes back with the seller's name, username, and member-since date.

#### 6. Find similar products

Found a listing you like? Paste its URL and find similar products — you can queue up several listings in one run:

```json
{
  "What to scrape": "Similar products",
  "Listing URLs": [
    "https://www.ouedkniss.com/renault-clio-d57354225",
    "https://www.ouedkniss.com/peugeot-208-d59321100"
  ]
}
```

Each returned row is tagged with `attributes.slot = "similar"`, and `attributes.similarToId` records which source listing produced it, so results from several URLs stay easy to group.

### Input

Only `What to scrape` is required. All other fields have defaults. Every reference field takes a full page URL, pasted straight from the browser's address bar — the Actor rejects bare IDs and slugs with a message telling you exactly what to paste instead.

The Console form groups the inputs into three collapsible sections — **Filters**, **Limits & performance**, and **Output & debugging** — under the mode picker and its related fields.

#### What to scrape

| Field | Description | Default |
| --- | --- | --- |
| `What to scrape` | `Search by keyword` / `Browse a category` / `Specific listing URLs` / `Category menu (full tree)` / `Sponsored listings (banner)` / `Seller listings` / `Similar products` | (required) |
| `Search keyword` | Search term, when `What to scrape` = "Search by keyword" | — |
| `Category page URL` | The category page URL, pasted from the address bar (e.g. `https://www.ouedkniss.com/automobiles_vehicules/1`). Meaning depends on `What to scrape`: the category to browse (`Browse a category`, required), the banner scope (`Sponsored listings`), or the sub-tree root (`Category menu`). Empty = site-wide banner / full tree walk. Ignored in the other modes. | — |
| `Seller page URL` | The seller to scrape, when `What to scrape` = "Seller listings". Paste a store page URL (`https://www.ouedkniss.com/store/290/dyalkom/`) or a member profile URL (`https://www.ouedkniss.com/membre/13412324`) — the URL decides which kind of seller it is. | — |
| `Listing URLs` | One or more listing page URLs (e.g. `https://www.ouedkniss.com/renault-clio-d57354225`), used by both modes that start from a listing page: `Specific listing URLs` (full detail data per URL) and `Similar products` (similar items per URL). | — |
| `Language` | Which language to fetch field values in (`Arabic` / `French` / `English`) | `French` |

#### Limits & performance

| Field | Description | Default |
| --- | --- | --- |
| `Maximum results` | Max listings to push. Applies in every mode. `0` = no cap | `200` |
| `Maximum pages` | Max API requests that list results. 48 listings per page (24 for member-profile listing grids). `0` = no cap. Ignored in `Category menu` and `Sponsored listings` (those don't paginate). | `20` |
| `How deep to walk the menu` | Menu-tree walk depth (top level = 0), used by `Category menu` | `2` |
| `Requests per minute` | Rate limit per worker | `30` |
| `Proxy fallback attempts` | How many proxy IPs to try at the start of a run when the site blocks the actor's exit IP. The first IP that returns data is reused for the rest of the run. No effect locally. | `5` |

#### Output & debugging

| Field | Description | Default |
| --- | --- | --- |
| `Include real phone + email` | Also fetch contact info (a couple of extra API calls). **Effective in `Specific listing URLs` mode only** — per-listing phone/email the site gates behind a click. The other modes never fetch a surface that carries contact data, so the toggle does nothing there. | `false` |
| `Skip duplicate listings` | Skip listings already pushed earlier in this run | `true` |
| `Debug: include raw API response` | Add a `raw` field with the original API response to each record | `false` |

### Filters

**Search by keyword**, **Browse a category** and **Seller listings** (store grids) can be narrowed with a flat set of `Filter by…` inputs, grouped in their own form section. All are optional; the other modes ignore them.

| Field | Description | Default |
| --- | --- | --- |
| `Filter by wilaya (region)` | Wilaya (province) IDs. Discover them by running `Category menu` first | — |
| `Filter by commune (city)` | Commune IDs. More granular than wilaya | — |
| `Minimum price (DZD)` | Min price in DZD (inclusive). Leave empty for no lower bound | — |
| `Maximum price (DZD)` | Max price in DZD (inclusive). Leave empty for no upper bound | — |
| `Only listings with photos` | Only listings with at least one photo | `false` |
| `Only listings with a price` | Only listings with a numeric price (excludes 'contact for price') | `false` |
| `Only listings that accept trade-in` | Only listings where the seller accepts trade-in | `false` |
| `Only listings with delivery` | Only listings where the seller offers delivery | `false` |
| `Filter by seller type` | `Any` (default), `Individual sellers only`, or `Stores only`. Map to the site's own enum; other values are rejected by the server. | `Any` |
| `Sort by` | `Most recently bumped` or `Most recently posted`. Price-sort is not supported by the public API | `Most recently bumped` |
| `Items per page` | Items per page, max 48 | `48` |

### Output

One JSON object per listing, in the **Dataset** tab. Every field is a scalar or a list of scalars — no nested objects (except the free-form specs map) — so the dataset table view shows clean values instead of "N fields" badges.

The dataset has three views that you can switch between from the view tabs at the top of the table:

- **Listings** — card projection (title, price, location, category, timestamps). The default. Use this for any mode except `Category menu`.
- **Category menu** — category-tree projection (category name, slug, parent breadcrumb, depth, rank). Use this for `Category menu` runs; the **Listings** view shows only `id` + `scrapedAt` for those rows.
- **Listing details** — full detail-page projection (adds description, seller info, contact info, gallery count, specs). Use this for `Specific listing URLs` runs with `Include real phone + email` on.

#### Organic row

`search` / `category` / `directUrls` runs produce rows like this:

```json
{
  "id": "57354225",
  "url": "https://www.ouedkniss.com/renault-clio-d57354225",
  "title": "Renault Clio 2019 essence",
  "description": "Clio IV intens, full options, premier propriétaire…",
  "condition": "used",
  "priceAmount": 1850000,
  "priceNegotiable": true,
  "categoryPath": "Véhicules › Automobiles › Renault",
  "categoryId": "2487",
  "wilayaCode": "16",
  "wilayaName": "Alger",
  "commune": "Hydra",
  "street": null,
  "sellerId": "13412324",
  "sellerName": "Moon Lite",
  "sellerUsername": "moonlite",
  "sellerType": "individual",
  "sellerProfileUrl": "https://www.ouedkniss.com/membre/13412324",
  "sellerMemberSince": null,
  "sellerPhoneNumbers": ["0555000000"],
  "sellerPhoneIntls": ["+213555000000"],
  "sellerPhoneWhatsapps": [true],
  "sellerPhoneVibers": [false],
  "sellerPhoneTelegrams": [false],
  "sellerEmails": ["moon@example.com"],
  "storeId": null,
  "storeName": null,
  "storeImageUrl": null,
  "storeProfileUrl": null,
  "likeCount": null,
  "images": ["https://cdn.ouedkniss.com/…/01.jpg", "https://cdn.ouedkniss.com/…/02.jpg"],
  "primaryImageUrl": "https://cdn.ouedkniss.com/…/01.jpg",
  "video": null,
  "postedAt": "2025-12-01T10:23:00.000Z",
  "lastBumpedAt": "2026-01-14T08:11:00.000Z",
  "scrapedAt": "2026-08-25T19:30:00.000Z",
  "attributes": { "Kilométrage": "80 000 km", "Carburant": "Essence" }
}
```

#### Sponsored-listing row

`Sponsored listings (banner)` runs produce rows with the banner's store metadata — the banner API doesn't return description, location, category, or specs:

```json
{
  "id": "57403311",
  "url": "https://www.ouedkniss.com/…-d57403311",
  "title": "MACBOOK AIR M4 16/256Gb",
  "description": null,
  "condition": "unspecified",
  "priceAmount": 138000,
  "priceNegotiable": false,
  "categoryPath": "",
  "categoryId": null,
  "wilayaCode": null,
  "wilayaName": null,
  "commune": null,
  "street": null,
  "sellerId": null,
  "sellerName": null,
  "sellerUsername": null,
  "sellerType": "unspecified",
  "sellerProfileUrl": null,
  "sellerPhoneNumbers": [],
  "sellerPhoneIntls": [],
  "sellerPhoneWhatsapps": [],
  "sellerPhoneVibers": [],
  "sellerPhoneTelegrams": [],
  "sellerEmails": [],
  "storeId": "8832",
  "storeName": "TRETEC informatique",
  "storeImageUrl": "https://cdn.ouedkniss.com/…",
  "storeProfileUrl": "https://www.ouedkniss.com/store/8832/",
  "likeCount": 12,
  "images": ["https://cdn.ouedkniss.com/…/01.jpg"],
  "primaryImageUrl": "https://cdn.ouedkniss.com/…/01.jpg",
  "video": null,
  "postedAt": null,
  "lastBumpedAt": null,
  "scrapedAt": "2026-08-25T19:30:00.000Z",
  "attributes": {}
}
```

#### Seller listings rows

`Seller listings` runs return the seller's listings only — one flat row per listing, identical in shape to every other organic row. For a personal account (`/membre/…` URL), each row also carries the seller's name, username, and member-since date, taken from the profile the run resolves. There is no seller-identity row of its own: if a store's name or URL matters downstream, it is already on the company's listings — or run `Specific listing URLs` on one of them for the full seller block.

#### Category-menu row

`Category menu (full tree)` runs produce rows like this (one per category node in the walk):

```json
{
  "id": "606",
  "categoryName": "Voitures",
  "categorySlug": "automobiles-voitures",
  "categoryIcon": "car.png",
  "categoryDelivery": true,
  "categoryRank": 1,
  "categoryDepth": 1,
  "parentTreeIds": "21",
  "parentTreeNames": "Automobiles & Véhicules",
  "parentTreeSlugs": "automobiles_vehicules",
  "scrapedAt": "2026-08-29T09:00:00.000Z"
}
```

#### Field-by-field

- **`priceAmount`** — numeric price in Algerian Dinar. `null` or `priceNegotiable: true` means "contact for price".
- **`categoryPath`** — breadcrumb from root to leaf, parts joined with `›`. The last segment is the leaf category. Empty for sponsored-listing rows.
- **`categoryId`** — site-side ID of the leaf category (the last segment of `categoryPath`). Useful for joining with the dataset produced by `Category menu` (where it appears as the row `id`).
- **`primaryImageUrl`** — first image URL (scalar copy of `images[0]`). Surfaces as a thumbnail in the dataset table; the `images` array renders as an "N items" badge, so the thumbnail needs its own field.
- **`wilayaCode`** — 58-wilaya code from Algeria's 2019 administrative reorganization. `null` for sponsored-listing rows.
- **`sellerPhoneNumbers`, `sellerPhoneIntls`, `sellerPhoneWhatsapps`, `sellerPhoneVibers`, `sellerPhoneTelegrams`** — parallel arrays; index `N` refers to the Nth phone. Empty unless `Include real phone + email` is on *and* the run is `Specific listing URLs` mode (per-listing contact). Always empty for sponsored-listing rows.
- **`sellerEmails`** — empty unless `Include real phone + email` is on *and* the run is `Specific listing URLs` mode (same surface as the phone arrays). Always empty for sponsored-listing rows.
- **`sellerMemberSince`** — when the seller account was created, ISO-8601 (assumed UTC). Populated on `Seller listings` rows for member accounts; null everywhere else (store pages don't expose an account-creation date).
- **`storeId` / `storeName` / `storeImageUrl` / `storeProfileUrl`** — populated only for sponsored-listing rows. The store's profile URL is built from the store ID (the banner API doesn't expose the store slug).
- **`likeCount`** — populated only for sponsored-listing rows.
- **`attributes`** — the one nested field. Free-form key/value for category-specific specs (mileage, fuel type, …) on listing rows. Similar-product rows carry `slot: "similar"` plus `similarToId` (the source listing's ID). Empty for sponsored-listing and category-tree rows.
- **`categoryName` / `categorySlug` / `categoryIcon` / `categoryDelivery` / `categoryRank` / `categoryDepth`** — populated only for rows from `Category menu`.
- **`parentTreeIds` / `parentTreeNames` / `parentTreeSlugs`** — breadcrumb from the root to (excluding) the current category-tree node, parts joined with `›`. Empty for non-category rows and for root-level nodes.

### Notes and limits

- **Needs Apify Proxy.** OuedKniss blocks the default datacenter IP range, so the Apify account running this Actor needs at least one proxy group enabled. Without one, runs appear to succeed but push zero records.
- **No phone numbers or emails unless `Include real phone + email` is on AND `What to scrape` is `Specific listing URLs`.** By default `sellerPhoneNumbers` and `sellerEmails` are empty arrays in every mode. Individual sellers' contact info is gated behind the site's click-to-reveal endpoints, which only the direct-URL mode triggers — member profiles expose no contact info anywhere on the web (the site directs buyers to its mobile app). `Seller listings`, `search`, `category`, `categories`, and `Similar products` never fetch a surface that carries contact data. The Actor prints a warning at startup if the toggle is enabled in a mode where it does nothing.
- **Sponsored-listings mode is opt-in** via `What to scrape` = "Sponsored listings (banner)". Use it when you specifically want the paid cross-listing banner; the response shape is much sparser than organic listings (only title, price, store metadata, one image).
- **No price-sort.** OuedKniss's public API only supports `Most recently bumped` and `Most recently posted` orderings.

### FAQ

**Can I scrape all of OuedKniss?**
Technically yes, but please don't. Use `Category menu` first to discover what you actually need, then narrow with `Filter by…`. Set `Maximum pages` and `Maximum results` to keep the run bounded.

**Does this Actor store cookies or login sessions?**
No. Every run is anonymous.

**How do I find a seller's URL?**
Open the store or seller page on ouedkniss.com and copy the URL from the address bar — `https://www.ouedkniss.com/store/290/dyalkom/` for stores, `https://www.ouedkniss.com/membre/13412324` for personal sellers. Paste the whole URL into the `Seller` input; the Actor extracts the ID and detects the seller type. On listing rows, the seller's profile URL is in `sellerProfileUrl`.

**My run hit `Maximum results` early. Why?**
Either the dataset already contains those listing IDs (dedupe is on by default) or the site returned fewer unique results than expected. Try raising `Maximum results` and `Maximum pages`, or turning `Skip duplicate listings` off to see raw counts.

### Contact

Bug reports, feature requests, or a custom scraper for a marketplace not yet covered? Open an issue on the Actor's Apify Console page or message the maintainer directly there.

# Actor input Schema

## `startMode` (type: `string`):

How to drive the scraper. `Search by keyword` and `Browse a category` return organic listings; `Seller listings` returns everything a specific seller — a business store or a personal account — has listed; `Similar products` fetches products similar to a given listing; `Sponsored listings (banner)` returns only the paid banner (different shape, store metadata only); `Category menu (full tree)` walks the site's category menu; `Specific listing URLs` enriches a hand-picked list of listing URLs with the full detail data.

## `searchQuery` (type: `string`):

Used when `What to scrape` = "Search by keyword". Free-text query, exactly as OuedKniss's own search box accepts it.

## `categoryUrl` (type: `string`):

The category page URL, pasted straight from the browser's address bar (e.g. `https://www.ouedkniss.com/automobiles_vehicules/1`). Meaning depends on `What to scrape`: for **Browse a category** it is the category to browse (required); for **Category menu** it is the sub-tree to start the walk from (omit to walk the full menu); for **Sponsored listings** it scopes the banner to that category (omit for the site-wide banner). Ignored in the other modes.

## `sellerUrl` (type: `string`):

Used when `What to scrape` = **Seller listings**. The seller whose listings you want — works the same for business stores and personal accounts. Paste the page URL straight from the address bar (e.g. `https://www.ouedkniss.com/store/290/dyalkom/` or `https://www.ouedkniss.com/membre/13412324`) — the URL decides which kind of seller it is. When **Include real phone + email** is on and the seller turns out to be a store, the run also emits one extra row with the store's public contact block (phones, emails, socials, address).

## `startUrls` (type: `array`):

The listing page URLs, pasted straight from the browser's address bar (e.g. `https://www.ouedkniss.com/renault-clio-d57354225`). The single input for the two modes that start from a listing page: **Specific listing URLs** scrapes each URL with the full detail data, and **Similar products** fetches the products similar to each URL (each row records its source listing in `attributes.similarToId`).

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

Stop after pushing this many listings. Applies in every mode. 0 = no cap.

## `maxPages` (type: `integer`):

Stop after this many API requests that list results. One page = one request (48 items per page for search/category, 24 for the member-profile listing grid). Use 0 for no cap. Ignored in **Category menu** and **Sponsored listings** (those don't paginate).

## `maxDepth` (type: `integer`):

Used only by **Category menu** to limit how deep the category tree is walked. Top level = 0. Default 2 keeps the run bounded — recursive walks grow quickly. Raise it to discover deeper branches (OuedKniss's full tree goes ~5 levels, but each extra level multiplies the number of API calls).

## `maxRequestsPerMinute` (type: `integer`):

Rate limit per worker. Anti-bot protection is currently low, but stay polite so the Actor stays viable long-term.

## `proxyMaxAttempts` (type: `integer`):

When the site blocks the actor's exit IP, the engine tries up to this many proxy IPs in sequence. The first one that returns a non-empty response is reused for the rest of the run. Default 5. Has no effect when running locally — local runs always use direct egress.

## `locale` (type: `string`):

Which language to fetch field values in. Defaults to French (most listings).

## `includeContactInfo` (type: `boolean`):

If true, the Actor also retrieves contact info. In **Specific listing URLs** mode it reveals the actual phone number and email that the site hides behind a click on each listing. In every other mode it has no effect — those runs never fetch a surface that carries contact data — and the phone/email arrays stay empty. The Actor prints a warning at startup if this is enabled in a mode where it does nothing.

## `dedupe` (type: `boolean`):

Skip listings whose ID has already been pushed in this run. Duplicates are dropped at the request-queue level too, so re-running the same Actor with the same settings is idempotent.

## `debugIncludeRaw` (type: `boolean`):

If true, each pushed listing includes a `raw` field with the unparsed API response.

## `filterRegionIds` (type: `array`):

Filter by wilaya IDs. Discover them by running `Category menu` first, or browse the menu in the site's own category tree. Leave empty to skip.

## `filterCityIds` (type: `array`):

Filter by commune IDs (more granular than wilaya). Leave empty to skip.

## `filterPriceMin` (type: `integer`):

Lower price bound, inclusive. Leave empty for no lower bound.

## `filterPriceMax` (type: `integer`):

Upper price bound, inclusive. Leave empty for no upper bound.

## `filterHasPictures` (type: `boolean`):

Only return listings that include at least one photo.

## `filterHasPrice` (type: `boolean`):

Only return listings with a numeric price (excludes 'contact for price').

## `filterExchange` (type: `boolean`):

Only return listings where the seller accepts trade-in.

## `filterDelivery` (type: `boolean`):

Only return listings where the seller offers delivery.

## `filterOrigin` (type: `string`):

Restrict results by whether the seller is a person or a business. **Any** (default) returns both. **Individual sellers only** returns listings posted by personal accounts. **Stores only** returns listings posted by verified stores / shops.

## `filterOrderByField` (type: `string`):

Sort order applied to search/category results. Price-sort is not supported by the public API.

## `filterCount` (type: `integer`):

Number of items per page (max 48).

## Actor input object example

```json
{
  "startMode": "search",
  "searchQuery": "voiture",
  "categoryUrl": "https://www.ouedkniss.com/automobiles_vehicules/1",
  "sellerUrl": "https://www.ouedkniss.com/store/290/dyalkom/",
  "startUrls": [],
  "maxItems": 200,
  "maxPages": 20,
  "maxDepth": 2,
  "maxRequestsPerMinute": 30,
  "proxyMaxAttempts": 5,
  "locale": "fr",
  "includeContactInfo": false,
  "dedupe": true,
  "debugIncludeRaw": false,
  "filterHasPictures": false,
  "filterHasPrice": false,
  "filterExchange": false,
  "filterDelivery": false,
  "filterOrigin": "",
  "filterOrderByField": "REFRESHED_AT",
  "filterCount": 48
}
```

# Actor output Schema

## `listings` (type: `string`):

Default dataset containing every row the Actor pushed during the run. View labels match the run mode: Listings for search/category/directUrls/topAnnounList, Category menu for the category-tree walk, Listing details for directUrls runs that include the contact-info enrichment.

# 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 = {
    "searchQuery": "voiture",
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("hamza325/ouedkniss-multi-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 = {
    "searchQuery": "voiture",
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("hamza325/ouedkniss-multi-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 '{
  "searchQuery": "voiture",
  "startUrls": []
}' |
apify call hamza325/ouedkniss-multi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hamza325/ouedkniss-multi-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/4t6qBq7f4yzMDRRK1/builds/JahNiRxcgboyCjutH/openapi.json
