# Kijiji Scraper & API: Canada Classifieds, New Ads, Price Drops (`usefulapi/kijiji-scraper`) Actor

Scrape Kijiji.ca listings by keyword, city and category: price, location, condition, car and rental fields. Watch mode returns only new ads and price drops since your last run. No seller personal data. From USD 0.50 per 1,000 listings.

- **URL**: https://apify.com/usefulapi/kijiji-scraper.md
- **Developed by:** [Useful API](https://apify.com/usefulapi) (community)
- **Categories:** E-commerce, Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Scraper & API: Canada classifieds, new ads and price drops

Get Kijiji.ca listings as clean data: title, price in dollars, location, condition, photos, and the full car and rental
fields (year, make, model, km, bedrooms, bathrooms, move-in date...). Search by **plain names** ("laptop" in "Calgary",
"Cars & Trucks" in "Alberta") or paste Kijiji search links. Turn on **watch mode** and each run returns only the
listings that are **new or changed price since your last run** (and, with a full check, the ones that are gone). No
seller personal data.

**From USD 0.50 per 1,000 listings.** No proxy costs added, no subscription.

### Why this one

- **Watch mode built in.** Schedule it hourly or daily: you get only new ads and price changes (with the previous
  price). On newest-first category searches a check stops at the first page with nothing new, so it is cheap; turn on
  "Full check every run" to also catch price changes on older listings and listings that disappeared (sold or removed).
  Unchanged listings are read but not billed as rows.
- **Plain input.** Type "Calgary", "City of Toronto", "Ville de Montréal", "Alberta" or "Canada", and category names like
  "Cars & Trucks", "Long Term Rentals" or "Free Stuff". No hunting for location codes or building URLs (codes and pasted
  Kijiji links work too).
- **Clean, typed fields.** Prices in dollars (Kijiji stores cents), price type (fixed, free, please contact, swap),
  the old price after a drop, Kijiji's own price rating on cars (great / good / fair), numbers as numbers, dates as ISO,
  and every attribute with Kijiji's own label ("Automatic", "2 + Den", "Used - Like new"), never a guessed one.
- **Privacy-safe by design.** No seller names or user ids. Phone numbers and emails typed into titles and description
  snippets are removed, and for private sellers street addresses and VINs too (best effort: unusual spellings can slip
  through). A private seller's location is cut to the city and postal area (first 3 characters), with coordinates
  rounded to about 1 km, and when the listing link would repeat a removed phone or address, the link uses Kijiji's
  plain form (`.../listing/<id>`, which opens the same ad). Dealers and property managers keep their business address
  and VIN. Useful if your company has to stay on
  the right side of PIPEDA.
- **Polite and robots.txt-safe.** One request every few seconds, no proxy, and every URL is checked against Kijiji's
  robots.txt before it is read. Filters (price, seller type, age, words) run after each page is read, because Kijiji asks
  robots not to use its filter parameters.
- **A summary per search:** Kijiji's total result count, pages read, rows saved, and the min / median / max price.

### What people use it for

- **Deal alerts:** a watch on "Cars & Trucks" in your city, or "macbook pro" under USD 1,500, run every hour.
- **Car dealers and buyers:** price, km, year, make, model and Kijiji's price rating across a region.
- **Rental market data:** asking rents by bedrooms, unit type and postal area for a city, week after week.
- **Resellers:** what sells for how much, and how fast listings disappear ("gone" rows in watch mode).
- **Research:** classifieds supply and prices across Canadian provinces.

### Input

| Field | What it does |
|---|---|
| Keywords | One search per line. Each keyword runs in every location and category. Leave empty to list a whole category. |
| Locations | Kijiji city, region or province names, or codes. Empty = all of Canada. |
| Categories | Kijiji category names or codes. Empty = all categories. |
| Or Kijiji search URLs | Search links copied from kijiji.ca (`kijiji.ca/b-...`). Anything after "?" is dropped (see "Good to know"). |
| Max rows per search | Stops a search after this many rows (default 100). |
| Max pages per search | 40 listings a page (default 25). In watch mode, how deep each check reads. |
| Watch mode | Save only new and price-changed listings since the last run with the same watch name. |
| Full check every run | Watch mode reads up to Max pages every run (price changes deeper down, gone listings). |
| Watch name | Separate memories for separate watch jobs. |
| Min / max price, seller type, max age, title must include, exclude words | Filters, applied after reading. |
| Include top ads | Kijiji's paid top ads (off by default: they repeat on every page and often come from other cities). |

Example: every private-seller snowblower under USD 800 in Alberta and Halifax:

```json
{
  "keywords": ["snowblower"],
  "locations": ["Alberta", "Halifax"],
  "sellerType": "owner",
  "maxPrice": 800
}
```

Example: hourly watch on new rentals in Toronto:

```json
{
  "categories": ["Long Term Rentals"],
  "locations": ["City of Toronto"],
  "watchMode": true,
  "watchName": "toronto-rentals",
  "maxPagesPerSearch": 5
}
```

### Output

One row per listing. Main columns (all rows): `listingId`, `url`, `title`, `price`, `currency`, `priceType`,
`originalPrice`, `priceDrop`, `categoryId`, `categoryName`, `locationName`, `province`, `postalArea`, `latitude`,
`longitude`, `locationPrecision`, `sellerType` (owner / business), `sellerVerified`, `condition`, `postedAt`,
`sortedAt` (last bump), `isTopAd`, `imageCount`, `imageUrls`, `description` (Kijiji's short snippet), `attributes`,
`position`, `searchLabel`, `searchUrl`, `scrapedAt`.

Cars and trucks add `vehicleYear`, `vehicleMake`, `vehicleModel`, `vehicleTrim`, `mileageKm`, `transmission`,
`fuelType`, `drivetrain`, `bodyType`, `colour`, `seats`, `doors`, `priceRating`, `msrp`, and for dealers `vin`, `carfaxUrl` and
`stockNumber`. Rentals add `unitType`, `bedrooms`, `bedroomsLabel`, `bathrooms`, `areaSqft`, `furnished`, `petsAllowed`,
`agreementType`, `availableFrom`, `parkingSpots`, `heatIncluded`, `hydroIncluded`, `waterIncluded`. Watch mode adds
`changeType` (new / price_changed / gone), `previousPrice` and `firstSeenAt`.

Example row (a dealer car from a real run on 17 Oct 2026, shortened):

```json
{
  "listingId": "1724470882",
  "url": "https://www.kijiji.ca/v-cars-trucks/city-of-toronto/2022-honda-civic-touring-auto-navi-leather-sunroof-camera-b-spot/1724470882",
  "title": "2022 Honda Civic TOURING AUTO NAVI LEATHER SUNROOF CAMERA B/SPOT",
  "price": 21750,
  "currency": "CAD",
  "priceType": "FIXED",
  "priceRating": "GOOD",
  "priceNote": "PLUS_TAXES",
  "categoryName": "Cars & Trucks",
  "locationName": "City of Toronto",
  "province": "Ontario",
  "postalArea": "M3J",
  "sellerType": "business",
  "condition": "Used",
  "vehicleYear": 2022,
  "vehicleMake": "Honda",
  "vehicleModel": "Civic",
  "mileageKm": 123730,
  "fuelType": "Gas",
  "drivetrain": "Front-wheel drive (FWD)",
  "bodyType": "Sedan",
  "colour": "Grey",
  "sortedAt": "2026-10-17T15:32:15.000Z"
}
```

The run summary (key-value store, record `OUTPUT`) gives, per search: Kijiji's total count, sort order, pages read,
rows saved, rows filtered out, duplicates skipped, watch counts, min / median / max price, and why it stopped.

### Pricing

Pay per event, no platform usage on top:

| Event | Price |
|---|---|
| Listing saved (a row) | USD 0.0005 (USD 0.50 per 1,000) |
| Search page read (40 listings) | USD 0.0003 |
| Run start | USD 0.002 |

A normal run of 1,000 listings costs about USD 0.51 (1,000 rows + 26 pages + start). A watch check that finds nothing
new costs only its pages: on a newest-first category search it usually stops after 1 page, USD 0.0023 including the start. Set "Maximum cost per run" and the run
stops cleanly before it, keeping every row already saved.

### Good to know

- **Sort order:** Kijiji shows keyword searches by best match and category pages without a keyword newest first. The
  tool cannot change the sort (Kijiji's robots.txt asks robots not to use its sort parameter). For "newest first" with a
  keyword, search the category and use "Title must include".
- **Search links with filters:** anything after "?" in a pasted Kijiji link (price range, "for sale by", sort, radius)
  is dropped for the same robots.txt reason; the tool's own filters do the same job after reading.
- **Gone listings** are reported only when both the last run and this run read the whole search: turn on "Full check
  every run", and for big searches raise "Max pages per search" or narrow the search.
- **Top ads** are never part of watch mode (they rotate on every page and would come back as new each run).
- One run saves at most 200,000 rows; split bigger jobs over several runs.
- **Description** is Kijiji's short results snippet (about 200 characters), not the full ad text, and photos are links,
  not copies.
- **Speed:** about 15 pages (600 listings) a minute, on purpose.
- If Kijiji refuses a page or asks to slow down (HTTP 403 or 429), that search stops and says why; two in a row stop the
  whole run. It never pushes harder or switches to a proxy.
- Street addresses that private sellers type into titles or snippets are removed too (best effort: unusual spellings
  can slip through, and a rare product name that looks like an address can be shortened).

### FAQ

**Is this a Kijiji API?** Kijiji has no public listings API. This Actor gives you API-style access: run it from code or
a schedule and read clean JSON, CSV or Excel.

**Can I get the seller's name or phone number?** No. The tool never reads Kijiji's contact or phone endpoints and leaves
seller identity out on purpose.

**Does it cover Quebec in French?** Yes, Quebec listings come through like the rest (titles as the seller wrote them).

**How fresh is the data?** Every run reads Kijiji live.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/usefulapi~kijiji-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords":["macbook pro"],"locations":["Vancouver"],"maxListingsPerSearch":50}'
```

### Responsible use

The tool reads public search pages slowly and leaves out personal data. You are responsible for how you use the data
and for following the laws and terms that apply to you. Not affiliated with Kijiji.

# Actor input Schema

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

What to search for, one search per line (for example: macbook pro, f-150, 2 bedroom). Each keyword is searched in every location and category below. Leave empty to list a whole category.

## `locations` (type: `array`):

City, region or province names as Kijiji writes them (Calgary, City of Toronto, Edmonton Area, Ville de Montréal, Alberta, Canada) or Kijiji location codes (1700199). Empty = all of Canada. Unknown names stop the run with the closest matches.

## `categories` (type: `array`):

Kijiji category names (Cars & Trucks, Laptops, Long Term Rentals, Buy & Sell, Free Stuff...) or codes (174). Empty = all categories. Category pages without a keyword come newest first, which suits watch mode best.

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

Search pages copied from kijiji.ca (they start with kijiji.ca/b-...). Anything after "?" (Kijiji's sort and filter settings) is dropped, because Kijiji's robots.txt asks robots not to use those; use the filters below instead.

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

Stop a search after this many rows are saved.

## `maxPagesPerSearch` (type: `integer`):

Kijiji shows 40 listings a page. In watch mode this is how deep each check reads (25 pages = 1,000 listings).

## `watchMode` (type: `boolean`):

Remembers every listing each search returned. Each next run saves only listings that are new or changed price since the last run with the same watch name (changeType new / price_changed, with previousPrice). On newest-first category searches a run stops at the first page with nothing new, so it catches new listings cheaply; turn on "Full check every run" for price changes deeper down and for gone listings. Schedule it hourly or daily for alerts. Unchanged listings are read but not saved or billed as rows.

## `watchName` (type: `string`):

Keeps separate memories for separate watch jobs (for example "toronto-rentals"). Same name + same search = compared with the last run.

## `watchFirstRunOutput` (type: `boolean`):

Off = the first run only learns what is there (pays for pages, saves no rows) and alerts start from the second run.

## `reportGone` (type: `boolean`):

Save a row (changeType gone) for each listing that disappeared since the last run. Only when both runs read the whole search: needs "Full check every run" on newest-first searches, and Max pages high enough for the whole search.

## `includeUnchanged` (type: `boolean`):

Save every listing with its changeType (new, price_changed, unchanged). Billed per row like a normal run.

## `watchFullScan` (type: `boolean`):

Newest-first category searches normally stop at the first page with nothing new (cheap, catches new listings). Turn this on to read up to Max pages every run: price changes on older listings are caught, and gone listings can be reported once a run reads the whole search.

## `resetWatch` (type: `boolean`):

Start this watch name from scratch: everything counts as new again.

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

Keep listings at or above this price. Listings without a price ("Please contact", swap) are left out when a price filter is set.

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

Keep listings at or below this price. Free listings count as 0.

## `sellerType` (type: `string`):

From Kijiji's "For sale by" / "For rent by" field.

## `maxAgeDays` (type: `integer`):

Keep listings posted or bumped in the last N days. In newest-first searches the run also stops reading once pages get older.

## `titleMustInclude` (type: `array`):

Keep only listings whose title contains all of these words (case does not matter). Useful because Kijiji's best-match search also returns accessories.

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

Leave out listings whose title or description snippet contains any of these (for example: wanted, broken, parts).

## `includeTopAds` (type: `boolean`):

Kijiji's paid top ads (often from other cities, repeated on every page). Off by default; when on, the first page's are added once. Never in watch mode.

## `includeDescription` (type: `boolean`):

The short description Kijiji shows in results (about 200 characters), with phone numbers and emails removed.

## `includeAttributes` (type: `boolean`):

Every attribute Kijiji lists for the item, with Kijiji's own labels (condition, brand, payment, amenities...). The main vehicle and rental fields are columns either way.

## Actor input object example

```json
{
  "keywords": [
    "laptop"
  ],
  "locations": [
    "Calgary"
  ],
  "categories": [],
  "maxListingsPerSearch": 100,
  "maxPagesPerSearch": 25,
  "watchMode": false,
  "watchName": "default",
  "watchFirstRunOutput": true,
  "reportGone": true,
  "includeUnchanged": false,
  "watchFullScan": false,
  "resetWatch": false,
  "sellerType": "any",
  "includeTopAds": false,
  "includeDescription": true,
  "includeAttributes": true
}
```

# Actor output Schema

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

One row per listing (in watch mode: per new, price-changed or gone listing). Download as CSV, Excel or JSON.

## `summary` (type: `string`):

Per search: Kijiji's total count, pages read, rows saved, filtered out, watch counts, min/median/max price, and why a search or the run stopped.

# 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 = {
    "keywords": [
        "laptop"
    ],
    "locations": [
        "Calgary"
    ],
    "categories": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("usefulapi/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 = {
    "keywords": ["laptop"],
    "locations": ["Calgary"],
    "categories": [],
}

# Run the Actor and wait for it to finish
run = client.actor("usefulapi/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 '{
  "keywords": [
    "laptop"
  ],
  "locations": [
    "Calgary"
  ],
  "categories": []
}' |
apify call usefulapi/kijiji-scraper --silent --output-dataset

```

## MCP server setup

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