# Tutti.ch Scraper — Swiss Classifieds, Prices & Sellers (`crawloop/tutti-ch-scraper`) Actor

Scrape tutti.ch classifieds by keyword, category, canton, or URL. Each row has the CHF price, location, seller, and photos. Monitor mode saves only new ads and price changes.

- **URL**: https://apify.com/crawloop/tutti-ch-scraper.md
- **Developed by:** [Andrej Kiva](https://apify.com/crawloop) (community)
- **Categories:** E-commerce, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 listings

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Tutti.ch Scraper

> **Disclaimer:** Unofficial tool — not affiliated with, sponsored by, or endorsed by SMG Swiss Marketplace Group AG or its affiliates. Data is read from publicly accessible search results and listing records only. No login. You are responsible for complying with applicable law (including GDPR where personal data appears) and the site’s terms. No warranty on accuracy or availability. Provided for informational and research use.

**Tutti.ch scraper** for Swiss classifieds. Each row is one listing: asking price in CHF, canton, seller, and photo. Search by keyword, category, and canton, or paste a results URL. Use it as a **tutti.ch API alternative** from **Python**, **Node.js**, **cURL**, or **Apify MCP**.

**Best for:** phone and goods comps, used cars, property and job leads, and a scheduled run that saves only new ads and price changes.

| Actor | Role |
| :--- | :--- |
| Tutti.ch Scraper ◄── you are here | Swiss classifieds: CHF price, canton, seller, new-ad monitor |
| [Leboncoin Scraper](https://apify.com/crawloop/leboncoin-scraper) | France classifieds |
| [Marktplaats Scraper](https://apify.com/crawloop/marktplaats-scraper) | Netherlands classifieds |
| [Facebook Marketplace Scraper](https://apify.com/crawloop/facebook-marketplace-scraper) | Marketplace listings |
| [Sahibinden Scraper](https://apify.com/crawloop/sahibinden-scraper) | Turkey classifieds |
| [mobile.de Scraper](https://apify.com/crawloop/mobile-de-scraper) | Germany car listings |
| [Subito Real Estate Scraper](https://apify.com/crawloop/subito-real-estate-scraper) | Italy property listings |

### When to use this Actor

- **A keyword in one category** — Set Keyword to iphone and Category to Phones. Add a canton such as ZH if you want one region.
- **Cars, property, furniture, or jobs** — Pick the category and leave the keyword empty for the latest ads in that section.
- **A results page you already opened** — Paste the search URL. A link with a search token is read as that saved search. Keyword and category are ignored when Search URLs is set.
- **One listing** — Paste the listing URL. The run saves that record, including the description and map point.
- **New ads and price cuts** — Turn on monitor mode and schedule the same search. The first run stores a baseline and saves nothing. Later runs save new listing ids and price changes.

### When not to use this Actor

- **Other Swiss marketplaces** — This Actor reads tutti.ch only.
- **A national dump in one run** — Cap `maxItems` (150). Split by category or canton.
- **Private messages or accounts** — No login, and no inbox.

### Key features

- **Search cards** — Title, CHF price, town, canton, postcode, seller name, seller type, and the first photo. Default **256 MB**. No browser.
- **Optional listing record** — Description, photo gallery, map coordinates, and attribute pairs. A phone or email is included only when the seller wrote it in the public description.
- **Keyword, category, canton, and price** — Cars, phones, real estate, furniture, bicycles, and jobs, or every category at once.
- **Pasted URL** — Search links, search tokens, and single listing pages.
- **Monitor** — Named key-value store of listing id and last price for that search. The seed run does not open listing records.

### Input

| Field | Required | Default | What it does |
| :--- | :--- | :--- | :--- |
| Search or listing URLs | No | — | Wins over keyword and category. |
| Keyword | No | iphone | Search text when URLs are empty. |
| Category | No | Phones | Section used when URLs are empty. |
| Canton | No | — | Short code, such as ZH or GE. |
| Minimum / maximum price | No | — | Asking price in CHF. |
| Sort | No | Newest | Newest, oldest, cheapest, most expensive, or relevance. |
| Language | No | German | Labels and the listing URL. |
| Max age (hours) | No | — | Stop once listings are older than this. |
| Fetch listing details | No | off | Description, gallery, coordinates, and attributes. |
| Maximum listings | No | 30 | Rows to save, up to 150. |
| Monitor mode | No | off | Baseline first, then new ids and price changes. |
| Monitor state store | No | tutti-monitor | Named store for the baseline. |
| Proxy | No | Swiss residential | Required from the cloud. Usage for the proxy is on your account. |

```json
{
  "keyword": "iphone",
  "category": "cellPhones",
  "canton": "ZH",
  "maxPrice": 800,
  "sort": "newest",
  "includeDetails": true,
  "maxItems": 30,
  "monitorMode": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "CH"
  }
}
```

### Output

| Field | Meaning |
| :--- | :--- |
| `id` | Listing id |
| `url` | Public listing page |
| `title` | Listing title |
| `price` | Asking price in CHF |
| `priceLabel` | Price text from the listing |
| `currency` | `CHF` |
| `category`, `categoryId` | Section label and id |
| `location`, `canton`, `cantonCode`, `postcode` | Place |
| `postedAt` | When the listing was posted |
| `sellerName`, `sellerId`, `sellerType` | Public alias, account id, `private` or `pro` |
| `image`, `images` | First photo, then the gallery when details are on |
| `description` | Listing text when details are on |
| `latitude`, `longitude` | Map point when details are on |
| `attributes` | Attribute pairs from the listing record |
| `email`, `phone` | Present only when written in the public description |
| `changeType` | `new` or `price_change` on monitor runs |
| `previousPrice` | Prior asking price when `changeType` is `price_change` |
| `scrapedAt` | UTC timestamp |

```json
{
  "id": "83303214",
  "url": "https://www.tutti.ch/de/vi/iphone-18-pro-256gb/83303214",
  "title": "iPhone 18 Pro 256Gb",
  "price": 1300,
  "priceLabel": "1'300.-",
  "currency": "CHF",
  "category": "Handys",
  "categoryId": "cellPhones",
  "location": "Lugano",
  "canton": "Tessin",
  "cantonCode": "TI",
  "postcode": "6900",
  "sellerName": "Erik",
  "sellerType": "private",
  "image": "https://c.tutti.ch/big/3297308070.jpg"
}
```

### Use cases

- Compare asking prices for one phone model and keep `sellerType` so business ads are separate from private sellers.
- Pull used cars in one canton with a price cap, then turn on listing details for mileage and condition when the record includes them.
- Schedule monitor mode each morning and export only listings whose price moved.
- For France, run [Leboncoin Scraper](https://apify.com/crawloop/leboncoin-scraper). For the Netherlands, run [Marktplaats Scraper](https://apify.com/crawloop/marktplaats-scraper).

### Integration examples

Replace `APIFY_TOKEN`. The slug below is the Store name.

#### Node.js

```javascript
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor("crawloop/tutti-ch-scraper").call({
  keyword: "iphone",
  category: "cellPhones",
  canton: "ZH",
  maxItems: 30,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("crawloop/tutti-ch-scraper").call(run_input={
    "keyword": "bmw",
    "category": "cars",
    "maxItems": 30,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(len(items))
```

#### cURL

```bash
curl -X POST "https://api.apify.com/v2/acts/crawloop~tutti-ch-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keyword":"iphone","category":"cellPhones","maxItems":30}'
```

### MCP and AI assistants

Use this Actor from AI tools via [Apify MCP](https://docs.apify.com/platform/integrations/mcp).
Connect your Apify account, then call this Actor by its Store name `crawloop/tutti-ch-scraper`.

Example prompts:

- "Run Tutti.ch Scraper for iphone in canton ZH and return price, location, and seller type as JSON"
- "Scrape tutti.ch cars under a CHF price cap and summarize asking price by canton"
- "Run Tutti.ch Scraper in monitor mode for sofas, then use Leboncoin Scraper for the same goods in France"

### Suite next step

This Actor covers Swiss classifieds. For the same kind of listing in France, run [Leboncoin Scraper](https://apify.com/crawloop/leboncoin-scraper). For the Netherlands, run [Marktplaats Scraper](https://apify.com/crawloop/marktplaats-scraper).

### FAQ

**Does this open every listing record?**
Only when Fetch listing details is on, or when you paste one listing URL. Monitor seed runs skip those records and save nothing.

**Which country is included?**
Switzerland, on tutti.ch. German, French, and Italian labels are available.

**What if the run is blocked?**
Keep Swiss residential selected and retry. A datacenter proxy is often refused.

**What does monitor mode skip?**
Listing ids already stored for that same search, unless the asking price changed. A quiet tick writes zero rows. The first run only fills the baseline.

**Are phone numbers included?**
Only when the seller wrote a phone number or email in the public description. Hidden contact buttons are not opened.

# Actor input Schema

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

tutti.ch search or listing URLs. A search URL can include a query or a search token. When this list is set, keyword and category are ignored.

## `keyword` (type: `string`):

Search text used when Search URLs is empty. Example: iphone, bmw, sofa.

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

Catalog section used when Search URLs is empty. Leave on All categories to search every section.

## `canton` (type: `string`):

Keep listings from this canton only. Use the short code, such as ZH, BE, GE, or TI.

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

Lowest asking price in Swiss francs. Leave empty for no minimum.

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

Highest asking price in Swiss francs. Leave empty for no maximum.

## `sort` (type: `string`):

Order of search results. Pasted search-token URLs use this sort as well.

## `language` (type: `string`):

Language for category labels and the listing URL.

## `maxAgeHours` (type: `integer`):

Stop when a listing is older than this many hours. Use with Newest sort on a schedule.

## `includeDetails` (type: `boolean`):

Open each saved listing for the description, photo gallery, map coordinates, and attributes. A pasted listing URL always includes those fields. Monitor seed runs skip them.

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

How many listings to save. Monitor mode examines this many matching cards and saves only new ids and price changes.

## `monitorMode` (type: `boolean`):

The first run for a search stores a baseline and saves nothing. Later runs save new listings and price changes only.

## `monitorStateStore` (type: `string`):

Named key-value store that keeps the baseline between scheduled runs.

## `proxyConfiguration` (type: `object`):

Swiss residential is required from the cloud. A datacenter session is often blocked. Platform usage for this proxy is paid by you.

## Actor input object example

```json
{
  "keyword": "iphone",
  "category": "cellPhones",
  "sort": "newest",
  "language": "de",
  "includeDetails": false,
  "maxItems": 30,
  "monitorMode": false,
  "monitorStateStore": "tutti-monitor",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CH"
  }
}
```

# Actor output Schema

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

Default dataset items — one listing per row.

# 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 = {
    "keyword": "iphone"
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawloop/tutti-ch-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 = { "keyword": "iphone" }

# Run the Actor and wait for it to finish
run = client.actor("crawloop/tutti-ch-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 '{
  "keyword": "iphone"
}' |
apify call crawloop/tutti-ch-scraper --silent --output-dataset

```

## MCP server setup

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