# Kijiji.ca Scraper - Listings, Prices & Seller Details (`abotapi/kijiji-scraper`) Actor

Scrape Kijiji.ca listings across property, vehicles, jobs, electronics, furniture, services, and more. Search by keyword and location or use any Kijiji URL. Extract titles, descriptions, prices, photos, GPS coordinates, seller details, and category-specific attributes.

- **URL**: https://apify.com/abotapi/kijiji-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Jobs, E-commerce
- **Features:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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?

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

## Kijiji.ca Scraper: Listings, Prices, Photos and Seller Contacts

Kijiji.ca Scraper pulls classified listings from Kijiji.ca across every category: property, vehicles, jobs, furniture, electronics, services and more. Search by keyword and location, or paste any Kijiji search, category or single-listing URL. A search result record carries the title, a description preview, price, the search page's own photo thumbnails, GPS coordinates, seller details and the category-specific attributes Kijiji shows on the listing; a pasted single-listing URL, or a search with Fetch listing details on, gets the full description text and the complete photo set instead. Switch on Incremental mode for recurring monitoring, download to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Every category, one actor.** The extractor adapts automatically to property, vehicles and general classifieds, so the same run handles an apartment search and a car search alike.
- **Rich records from the search page alone.** 20+ fields per listing straight off the results page, including a description preview, the search page's own photo thumbnails and GPS coordinates, no extra fetch required. Turn on Fetch listing details, or paste a single-listing URL, for the full description text and the complete photo set.
- **Optional detail enrichment.** Switch on view counts, seller phone and website, listing status, virtual tour or video, and dealer profile per run.
- **Two ways in.** Search by keyword and location across any Canadian city, region or province, or paste Kijiji search, category or single-listing links directly.
- **Price and sort filters that actually narrow.** Minimum and maximum price apply to a keyword search and a pasted URL alike, and a run walks the whole result set with no artificial page ceiling unless you set one.
- **Built for recurring monitoring.** Turn on Incremental mode to get only new, updated or reappeared listings on a schedule, or paste a previous run's id to collect just what's new since then.
- **Recovers on its own.** A run resumes from where it stopped after a platform migration or a manual restart, with no duplicate rows or duplicate charges, and automatically retries with a different connection when one is refused.

### Use Cases

- **Marketplace resellers and pickers:** track new and price-dropped listings across categories to source inventory worth flipping.
- **Rental and real estate research:** monitor new property listings in a neighbourhood, with price, layout attributes and photos, and get alerted only when something changes.
- **Car shopping and dealer tracking:** filter listings by price and body type, then watch a saved search for new inventory instead of refreshing the site by hand.
- **Local services and gig marketplaces:** pull job or services listings for a city to track posting volume and going rates.
- **Market and pricing analysts:** build a historical price dataset per category and region for trend analysis.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `id` | `"1700000000"` |
| `listingType` | `"AutosListing"` (also `"RealEstateListing"`, `"StandardListing"`, ...) |
| `title` | `"Sample listing title"` |
| `description` | `"Full listing description appears here."` |
| `url` | `"https://www.kijiji.ca/v-cars-trucks/city-of-toronto/sample-listing/1700000000"` |
| `price` / `priceType` | `12500.0` / `"FIXED"` |
| `currency` | `"CAD"` |
| `categoryId` | `174` |
| `locationName` | `"Toronto"` |
| `address` | `"1 Placeholder Crescent, Toronto, ON, M0M 0M0"` |
| `latitude` / `longitude` | `43.65` / `-79.4` |
| `imageUrls` / `imageCount` | one URL per photo (thumbnails from the search page, or the full set with Fetch listing details on) / `8` |
| `posterId` | `"1700000001"` |
| `sellerType` | `"PVT"` |
| `posterRating` / `posterVerified` | `4.5` / `false` |
| `activationDate` | `"2026-01-01T00:00:00.000Z"` |
| `isTopAd` | `false` |
| `attr_carmake` / `attr_noofseats` | `"ford"` / `"4"` |
| `views` | `342` |
| `phoneNumber` | `"4165550123"` |
| `sellerWebsiteUrl` | `"https://example.com"` |

Category-specific facts arrive as `attr_*` keys (for example `attr_carmake`, `attr_numberbedrooms`, `attr_dateavailable`), plus the complete raw attribute list in `attributesRaw`, so no field is ever dropped; a property, vehicle or general-classifieds listing carries the same shell shape, only the `attr_*` keys differ. With detail enrichment on, a listing can also carry `status`, `listingTypeOffer`, `endDate`, `virtualTourUrl`, `youtubeVideoId`, `requestViewingUrl`, `mlsAd` and, for a dealer or agency account, a `commercialProfile` object (business name, listing count, website, verification). Every record also carries derived aliases built from those same values: address parts (`street_address`, `locality`, `region`, `country`, `postalCode`), `dateFrom` / `dateTo`, `images` and `features` (the listing's own yes/no amenity flags), and for vehicle listings `brand`, `model`, `modelYear`, `vin`, `milage` (km), `bodyType`, `color`, `fuelType`, `transmission` (the page's label, or decoded from Kijiji's transmission code 0, 1 or 2 when the page supplies no label; empty for any other code), `driveWheelConfiguration`, `seats`, `numberOfDoors`, `condition` and `trim` (the trim or variant line, free text). `brand`, `bodyType`, `color`, `fuelType`, `driveWheelConfiguration` and `condition` use the page's label when it supplies one, otherwise the readable label for Kijiji's own attribute code (for example `suvcrossover` becomes `SUV, Crossover`, `gas` becomes `Gas`); `model` and any code outside the known list come back as Kijiji's own lowercase code, unchanged. In Incremental mode, every record also carries `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `changedFields`, `firstSeenAt` and `lastSeenAt`.

### How to Use

1. Pick a **mode**: `search` (keyword + location) or `url` (paste links).
2. Fill in the fields for that mode, and set any price filter or sort order you need.
3. Set **Max listings** (and, optionally, **Max pages**) to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search a keyword in one city:**

```json
{
  "mode": "search",
  "keywords": ["iphone"],
  "location": "city-of-toronto",
  "maxListings": 50
}
```

**Search with a price range and sort order:**

```json
{
  "mode": "search",
  "keywords": ["laptop"],
  "location": "vancouver",
  "minPrice": 200,
  "maxPrice": 800,
  "sortBy": "priceAsc",
  "maxListings": 20
}
```

**Browse every listing in a location, no keyword, with details on:**

```json
{
  "mode": "search",
  "keywords": [],
  "location": "calgary",
  "fetchDetails": true,
  "maxListings": 30
}
```

**Paste Kijiji category or listing URLs directly:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273",
    "https://www.kijiji.ca/b-cars-trucks/city-of-toronto/c174l1700273"
  ],
  "maxListings": 100
}
```

**URL mode link shapes.** Every shape below is supported and has an example in the input form:

| Shape | Example |
|---|---|
| Keyword search, all Canada | `https://www.kijiji.ca/b-canada/iphone/k0l0` |
| Keyword search in a location | `https://www.kijiji.ca/b-city-of-toronto/iphone/k0l1700273` |
| Category in a location | `https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273` |
| Category, all Canada | `https://www.kijiji.ca/b-cars-vehicles/canada/c27l0` |
| All categories in a location | `https://www.kijiji.ca/b-city-of-toronto/c0l1700273` |
| A later results page (the walk starts at that page) | `https://www.kijiji.ca/b-apartments-condos/city-of-toronto/page-2/c37l1700273` |
| Sorted / price-filtered link (filters are kept and applied) | `https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273?sort=priceAsc&price=500__1500` |
| Single listing | `https://www.kijiji.ca/v-apartments-condos/city-of-toronto/1-bedroom-apartment-for-rent-st-clair-ave-west-and-spadina-rd/1744550680` |
| Older listing link | `https://www.kijiji.ca/v-view-details.html?adId=1744309122` |
| Host without `www` | `https://kijiji.ca/b-canada/iphone/k0l0` |
| French-locale link (results are returned in English) | `https://www.kijiji.ca/b-grand-montreal/iphone/k0l80002?siteLocale=fr_CA` |

Seller profile pages, the `m.kijiji.ca` mobile host, the bare `/v/<id>` form and links to other sites are not supported: they are skipped with a warning that names the reason. In Search mode, a keyword that is a Kijiji ad number or a pasted Kijiji link is routed to that listing or page instead of being used as search text.

**Behaviour change:** the `urls` field now ships with one example of each shape as its default. A URL-mode run started with the list left untouched (or the field omitted through the API) scrapes those examples, bounded by Max listings, instead of finding nothing to do. Pass your own list to scrape something else.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/kijiji-scraper").call(run_input={
    "mode": "search",
    "keywords": ["iphone"],
    "location": "city-of-toronto",
    "maxListings": 50,
})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["price"], listing["url"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/kijiji-scraper').call({
  mode: 'search',
  keywords: ['iphone'],
  location: 'city-of-toronto',
  maxListings: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max pages and Max listings shape a run

**Max pages** bounds how many result pages are walked per search or pasted URL (40 listings per page); `0` walks until the run stops on its own, at the reported total, an empty page, or a page that returns nothing new to that walk. **Max listings** is the one setting that bounds how many listings are saved for the whole run; `0` collects everything available (paid plans only), limited only by Max pages and the natural end of results. A pasted single-listing URL returns at most one record (a price filter, a Resume-seeded id, or an UNCHANGED classification in Incremental mode can drop it to zero). With **Fetch listing details** on (the default), the full description text and complete photo set are always included; a handful of search-only fields are not available from the listing page itself and come back null, including `imageCount` (the photos in `imageUrls` are still complete), `posterRating`, `nearestIntersection`, `hasVirtualTour`, and every `attr_*_label` field (the labels for category-specific attributes, which only the search page's own filter metadata supplies). With detail off, a pasted single-listing URL still returns the full description and photo set, since the listing page has to be read either way to identify the listing; it drops the flat detail-only fields (`views`, `status`, and similar), but the nested `posterInfo` object, which can include the seller's phone number and website, is still copied through in full either way, so switching detail off does not withhold seller contact info for a pasted listing link the way it does for a search result.

#### Resume, automatic recovery, and Incremental mode

These are three separate features:

- **`resumeFromRunId`** continues one specific prior run (or dataset) as a delta: paste its run or dataset id and this run skips every listing already collected there, walking the full search again but returning, and charging for, only what's new. A page made entirely of listings you already have does not stop the walk early; it keeps going until it reaches genuinely new listings or the natural end of the search.
- **Automatic recovery** needs no input at all: if the platform migrates the run or it is manually restarted, the run picks its checkpoint back up (same search position, same collected ids) and continues, so a restart never re-collects or re-charges a listing already pushed.
- **Incremental mode** (`incrementalMode`) is for a search or URL list you run on a schedule. Each listing is classified against the actor's own memory of the last run for that search:

  | `changeType` | When it fires |
  | --- | --- |
  | `NEW` | Not seen in any previous run for this search. |
  | `UPDATED` | Seen before, and at least one field genuinely changed (`changedFields` lists which). |
  | `UNCHANGED` | Seen before, nothing changed. Not returned by default; turn on `emitUnchanged` to get it anyway. |
  | `REAPPEARED` | The listing was previously marked gone by an EXPIRED run for this search, and has now been scraped again. |
  | `EXPIRED` | Previously seen, not found in this run, and this run reached the natural end of every tracked search/URL. Only produced when `emitExpired` is on. |

  `emitExpired` gates more than which rows you see: only a run with `emitExpired` on, that also scans its whole tracked search or URL list to the natural end (no `maxListings`/`maxPages` cap reached, no `resumeFromRunId`, no page that failed to load), ever marks a listing as gone in the actor's memory. If that condition is never met, a listing that stops appearing simply keeps its last-known status; it is never silently marked gone, and `REAPPEARED` can therefore never fire for it either. `REAPPEARED` only exists at all for a listing that some earlier run had already marked gone this way, and is now back. `views` and `sortingDate` never trigger a false `UPDATED` (Kijiji advances both independently of any real edit); every other field, including `price` and `description`, participates unless you list it in **Ignore fields when detecting changes** (`ignoreFieldsForChanges`, incremental mode only). That input takes exact output field names or dotted paths such as `posterInfo.rating`; the listed fields stay in the output but never make a listing `UPDATED` or appear in `changedFields`. Changing the list takes effect immediately, without a one-time wave of `UPDATED` rows. The state key is built from the search settings: in search mode the pre-filled example list in **Kijiji URLs** is ignored when building it (it is not used by a search run), so a schedule keeps its saved state; a URL list you actually enter is part of the key.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape; the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | Search by keyword + location, or paste Kijiji URLs directly. |
| `keywords` | array | (none; prefilled with `["iphone"]`) | Search terms (Search mode). Each term runs as its own all-category search. Empty browses everything in the location. |
| `location` | string | `canada` (prefilled with `city-of-toronto`) | City, region, or province name, a Kijiji location slug, or a numeric location id. |
| `sortBy` | string | `relevance` | Result ordering: relevance, newest first, price low to high, or price high to low. Search mode only; Kijiji still inserts a few promoted ads at the top regardless of sort. |
| `urls` | array | one example of each supported link shape (see "URL mode" above) | Kijiji search, category or single-listing URLs (URL mode). Multiple supported; pagination walks forward from the page in each URL. |
| `minPrice` | integer | (none) | Minimum price in Canadian dollars. Applies to Search mode and URL mode alike. Once either bound is set, a listing with no numeric price (e.g. "Please Contact", "Free", "Swap") is dropped. |
| `maxPrice` | integer | (none) | Maximum price in Canadian dollars. Applies to Search mode and URL mode alike; see `minPrice` for the no-price-listing behavior. |
| `fetchDetails` | boolean | `true` | Open each listing's detail page for view count, seller phone + website, listing status, virtual tour / video, and dealer profile. |
| `maxPages` | integer | `0` | Optional bound on result pages walked per search/URL (40 listings per page). `0` walks until the run stops on its own: the reported total is reached, a page comes back empty, or a page returns nothing new to that walk. Does not cap how many listings are saved; use Max listings for that. |
| `maxListings` | integer | `20` | Maximum listings to save across the whole run. `0` collects every available listing (paid plans only), bounded only by Max pages and the natural end of results. |
| `resumeFromRunId` | string | (none) | ID of a previous run (or dataset) of this actor. Listings already there are skipped, so this run returns only new listings (a delta). |
| `incrementalMode` | boolean | `false` | Recurring monitoring: remembers the listings seen last time for this search and, by default, returns only NEW, UPDATED or REAPPEARED ones. |
| `stateKey` | string | (none; derived automatically from your filters) | Optional label for the tracked search when Incremental mode is on. |
| `emitUnchanged` | boolean | `false` | Incremental mode only: also return UNCHANGED listings (the full snapshot every run). |
| `emitExpired` | boolean | `false` | Incremental mode only: also return EXPIRED listings, and only when this run reached the natural end of every tracked search/URL. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode only: fields never counted as a change (`views` and `sortingDate` are always ignored). |
| `proxy` | object | Apify Residential, country CA | Connection settings. Kijiji.ca works most reliably on a Canadian residential connection. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": "1700000000",
  "listingType": "AutosListing",
  "title": "Sample listing title",
  "description": "Full listing description appears here.",
  "url": "https://www.kijiji.ca/v-cars-trucks/city-of-toronto/sample-listing/1700000000",
  "price": 12500.0,
  "priceRaw": 1250000,
  "priceType": "FIXED",
  "currency": "CAD",
  "categoryId": 174,
  "locationName": "Toronto",
  "address": "1 Placeholder Crescent, Toronto, ON, M0M 0M0",
  "latitude": 43.65,
  "longitude": -79.4,
  "nearestIntersection": ["Sample Road", "Example Avenue"],
  "imageUrls": ["https://media.kijiji.ca/api/v1/ca-prod-fsbo-ads/images/00/00000000-0000-0000-0000-000000000000?rule=kijijica-200-jpg"],
  "imageCount": 8,
  "posterId": "1700000001",
  "sellerType": "PVT",
  "posterRating": 4.5,
  "posterVerified": false,
  "activationDate": "2026-01-01T00:00:00.000Z",
  "isTopAd": false,
  "attr_carmake": "ford",
  "attr_noofseats": "4",
  "views": 342,
  "status": "ACTIVE",
  "phoneNumber": "4165550123",
  "sellerWebsiteUrl": "https://example.com"
}
```

### Plan Requirement

Kijiji.ca is most reliable over a Canadian residential connection, which is the default proxy setting (Apify Residential, country CA) and requires an Apify Starter plan or higher. A datacenter connection loaded Kijiji.ca search and category pages in a single check, but it was not tested at volume and the site may refuse datacenter addresses once a run makes many requests, so use residential for scheduled or large runs. The run automatically retries with a different connection when one is refused. A run that cannot reach Kijiji.ca at all still finishes successfully, with zero listings and a status message pointing at the proxy setting, rather than exiting as an error.

### FAQ

#### How much does it cost?

Billing has three parts: a start charge once per run (`apify-actor-start`), a charge for each listing pushed to the dataset (`apify-default-dataset-item`), and a detail-enrichment charge (`detail-enrichment`) for each pushed listing whose detail page was opened (turned on by **Fetch listing details**). Suppressed Incremental-mode rows (UNCHANGED, by default), skipped duplicates and listings that were not pushed are never billed. The **Pricing** tab shows the current rates; use **Max listings** to cap the cost of any run.

The dataset also ships saved views in the Console (listings overview, seller and contact, changes, detail enrichment, vehicles, and real estate and location) that group the same fields into tables; they do not change the output or the billing.

#### Is it legal to scrape Kijiji.ca?

This actor collects only publicly available classified-listing data. You are responsible for how you use it: follow Kijiji's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution, especially of seller contact details.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor and turn on **Incremental mode**. See "Resume, automatic recovery, and Incremental mode" above for exactly when `REAPPEARED` and `EXPIRED` can occur; a quiet run with nothing changed can legitimately return zero listings, which is not an error.

#### Why did my run return fewer listings than I expected?

**Max listings** and **Max pages** both cap a run; raise them, or set **Max listings** to `0`, to collect more. A price filter also drops any listing with no numeric price (e.g. "Please Contact", "Free", "Swap") once either bound is set.

#### Why does a fully blocked run finish "succeeded" with zero listings instead of failing?

An empty result really can mean nothing matched, so a run that scans nothing still finishes successfully rather than exiting as an error; the run's status message explains what happened and recommends a Canadian residential connection. If you're automating this, check the returned listing count (or the status message) rather than only the run status.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more classifieds data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏷️ <a href="https://apify.com/abotapi/leboncoin-fr-scraper"><b>Leboncoin FR</b></a><br>Pull public leboncoin.fr listings across cars, property, jobs, electronics, furniture...</td><td>🏷️ <a href="https://apify.com/abotapi/mudah-my-scraper"><b>Mudah.my Cars, Property, Jobs &amp; Marketplace Scraper</b></a><br>Scrape Mudah.my listings across every category: cars, motorcycles, property, mobiles...</td></tr>
<tr><td>🚗 <a href="https://apify.com/abotapi/trademe-co-nz-scraper"><b>Trade Me NZ Scraper</b></a><br>Scrape trademe.co.nz across all five sections: Marketplace goods and auctions, Property...</td><td>🏷️ <a href="https://apify.com/abotapi/tutti-ch-scraper"><b>Tutti.ch Scraper</b></a><br>Scrape listings from tutti.ch across all 23 categories, including vehicles, property...</td></tr>
<tr><td>🚗 <a href="https://apify.com/abotapi/willhaben-at-scraper"><b>Willhaben.at Scraper</b></a><br>Scrape Willhaben.at listings into clean JSON with 40+ structured fields, including...</td><td>🏷️ <a href="https://apify.com/abotapi/kleinanzeigen-de-scraper"><b>Kleinanzeigen.de Scraper</b></a><br>Scrape Kleinanzeigen.de ads by keyword, category, location, price, seller type, or search...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/kijiji-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Search by keyword + location, or paste Kijiji URLs directly.

## `keywords` (type: `array`):

Search terms (Search mode). Each term runs as a separate all-category search. Leave empty to browse every listing in the location.

## `location` (type: `string`):

City, region, or province name (e.g. 'Toronto', 'Vancouver', 'Ontario'), a Kijiji location slug ('city-of-toronto'), or a numeric location id. Defaults to all of Canada.

## `sortBy` (type: `string`):

Result ordering. Note: Kijiji inserts a few promoted ads at the top regardless of sort.

## `urls` (type: `array`):

Kijiji links (URL mode): search pages (keyword, with or without a location), category and all-categories location pages, paginated /page-N/ links (the walk starts at that page), links with sort/price filters (kept and applied), single listing links (/v-.../<adId> or the older v-view-details.html?adId=), the kijiji.ca host without www, and French-locale links. Multiple supported. Seller profile pages, the m.kijiji.ca host and other sites are skipped with a warning. Leaving this list at its default scrapes one example of each shape. Keywords, Location, and Sort by are search-only and have no effect here; Min/Max price still filters these results.

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

Minimum price filter in Canadian dollars. Leave empty for no minimum.

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

Maximum price filter in Canadian dollars. Leave empty for no maximum.

## `fetchDetails` (type: `boolean`):

Open each listing's detail page for view count, seller phone + website, listing status, virtual tour / video, and dealer profile. Slower but much richer.

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

Optional bound on result pages walked per search/URL (40 listings per page). Use 0 to walk the whole catalogue: the run stops on its own once Kijiji's reported total is reached, a page comes back empty, or a page returns no new listings. This does NOT cap the number of listings saved; use Max listings for that.

## `maxListings` (type: `integer`):

Maximum listings to save across the whole run. 0 = unlimited (paid plans only): collect every available listing, bounded only by Max pages and the natural end of results.

## `proxy` (type: `object`):

Kijiji.ca works most reliably on Apify Residential proxy with country = CA. Datacenter exits are frequently rejected. On failure the actor rotates to a fresh exit IP and can fail over to a backup gateway.

## `resumeFromRunId` (type: `string`):

ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only NEW listings (a delta). Combine both runs' datasets for the full set. Max listings then counts only the new listings. For a recurring/scheduled search, use Incremental mode below instead: it remembers the previous run itself, without pasting an id every time.

## `incrementalMode` (type: `boolean`):

For a search/URL list you run on a schedule (daily, weekly): the actor remembers the listings it saw last time (in a dedicated key-value store, keyed by this search) and, by default, only returns listings that are NEW, UPDATED (price, description, ... changed) or REAPPEARED. Unlike Resume above, you never need to paste a run or dataset id; it tracks itself. Off by default: a normal run always returns every matching listing.

## `stateKey` (type: `string`):

Optional label for the tracked search when Incremental mode is on, e.g. 'toronto-iphones'. Leave empty to let the actor derive one automatically from your search/URL filters (two different searches never share a baseline either way). Set this if you want a stable, human-readable name, or want to run the exact same search twice as two separate monitoring campaigns.

## `emitUnchanged` (type: `boolean`):

When Incremental mode is on, also return listings with no changes since last time (changeType 'UNCHANGED'), not just NEW/UPDATED/REAPPEARED. Off by default. Returning unchanged rows bills like any other listing — turn this on only if you need the full current snapshot every run, not just what changed.

## `emitExpired` (type: `boolean`):

When Incremental mode is on AND this run scans the search to its natural end (no Max listings/Max pages cap reached, no Resume from a previous run), also return one row per previously-seen listing no longer found (changeType 'EXPIRED'), using its last-known data. Off by default. Returning expired rows bills like any other listing.

## `ignoreFieldsForChanges` (type: `array`):

Incremental mode only: output fields that must never make a listing UPDATED or appear in changedFields, e.g. 'description' or a nested path like 'posterInfo.rating'. The fields stay in the output; they are only skipped when comparing with the last run. Always ignored regardless: views and sortingDate. Changing this list never causes a one-time wave of UPDATED rows.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "keywords": [
    "iphone"
  ],
  "location": "city-of-toronto",
  "sortBy": "relevance",
  "urls": [
    "https://www.kijiji.ca/b-canada/iphone/k0l0",
    "https://www.kijiji.ca/b-city-of-toronto/iphone/k0l1700273",
    "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273",
    "https://www.kijiji.ca/b-cars-vehicles/canada/c27l0",
    "https://www.kijiji.ca/b-city-of-toronto/c0l1700273",
    "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/page-2/c37l1700273",
    "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273?sort=priceAsc&price=500__1500",
    "https://www.kijiji.ca/v-apartments-condos/city-of-toronto/1-bedroom-apartment-for-rent-st-clair-ave-west-and-spadina-rd/1744550680",
    "https://www.kijiji.ca/v-view-details.html?adId=1744309122",
    "https://www.kijiji.ca/b-grand-montreal/iphone/k0l80002?siteLocale=fr_CA"
  ],
  "fetchDetails": true,
  "maxPages": 0,
  "maxListings": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CA"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

Individual listing records with price, location hierarchy, GPS coordinates, all image URLs, seller info, and category-specific attributes.

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

Run summary with totals, pages fetched, details fetched, and duration.

## `changes` (type: `string`):

Changes feed view of the listings dataset.

## `detail` (type: `string`):

Detail enrichment view of the listings dataset.

## `vehicles` (type: `string`):

Vehicles view of the listings dataset.

## `realEstate` (type: `string`):

Real estate & location view of the listings dataset.

# 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 = {
    "mode": "search",
    "keywords": [
        "iphone"
    ],
    "location": "city-of-toronto",
    "sortBy": "relevance",
    "urls": [
        "https://www.kijiji.ca/b-canada/iphone/k0l0",
        "https://www.kijiji.ca/b-city-of-toronto/iphone/k0l1700273",
        "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273",
        "https://www.kijiji.ca/b-cars-vehicles/canada/c27l0",
        "https://www.kijiji.ca/b-city-of-toronto/c0l1700273",
        "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/page-2/c37l1700273",
        "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273?sort=priceAsc&price=500__1500",
        "https://www.kijiji.ca/v-apartments-condos/city-of-toronto/1-bedroom-apartment-for-rent-st-clair-ave-west-and-spadina-rd/1744550680",
        "https://www.kijiji.ca/v-view-details.html?adId=1744309122",
        "https://www.kijiji.ca/b-grand-montreal/iphone/k0l80002?siteLocale=fr_CA"
    ],
    "maxPages": 0,
    "maxListings": 20,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "CA"
    },
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/kijiji-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 = {
    "mode": "search",
    "keywords": ["iphone"],
    "location": "city-of-toronto",
    "sortBy": "relevance",
    "urls": [
        "https://www.kijiji.ca/b-canada/iphone/k0l0",
        "https://www.kijiji.ca/b-city-of-toronto/iphone/k0l1700273",
        "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273",
        "https://www.kijiji.ca/b-cars-vehicles/canada/c27l0",
        "https://www.kijiji.ca/b-city-of-toronto/c0l1700273",
        "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/page-2/c37l1700273",
        "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273?sort=priceAsc&price=500__1500",
        "https://www.kijiji.ca/v-apartments-condos/city-of-toronto/1-bedroom-apartment-for-rent-st-clair-ave-west-and-spadina-rd/1744550680",
        "https://www.kijiji.ca/v-view-details.html?adId=1744309122",
        "https://www.kijiji.ca/b-grand-montreal/iphone/k0l80002?siteLocale=fr_CA",
    ],
    "maxPages": 0,
    "maxListings": 20,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "CA",
    },
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/kijiji-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 '{
  "mode": "search",
  "keywords": [
    "iphone"
  ],
  "location": "city-of-toronto",
  "sortBy": "relevance",
  "urls": [
    "https://www.kijiji.ca/b-canada/iphone/k0l0",
    "https://www.kijiji.ca/b-city-of-toronto/iphone/k0l1700273",
    "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273",
    "https://www.kijiji.ca/b-cars-vehicles/canada/c27l0",
    "https://www.kijiji.ca/b-city-of-toronto/c0l1700273",
    "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/page-2/c37l1700273",
    "https://www.kijiji.ca/b-apartments-condos/city-of-toronto/c37l1700273?sort=priceAsc&price=500__1500",
    "https://www.kijiji.ca/v-apartments-condos/city-of-toronto/1-bedroom-apartment-for-rent-st-clair-ave-west-and-spadina-rd/1744550680",
    "https://www.kijiji.ca/v-view-details.html?adId=1744309122",
    "https://www.kijiji.ca/b-grand-montreal/iphone/k0l80002?siteLocale=fr_CA"
  ],
  "maxPages": 0,
  "maxListings": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CA"
  },
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/kijiji-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/kijiji-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/Q197vGm0YmVfdU8Ib/builds/KTBx3Zyq1GBj2aJch/openapi.json
