# Tutti.ch Scraper: Swiss Classifieds Listings (`reventadata/tutti-scraper`) Actor

Scrape tutti.ch, the Swiss second-hand marketplace, by keyword: title, price, postcode, place, canton, posting time, category, seller and thumbnail. Optional detail pages add full text, coordinates and all image links. Unofficial.

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

## Pricing

from $2.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.

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

> **Unofficial.** This Actor is not affiliated with, authorised by, endorsed by, or in any
> way officially connected to tutti.ch. "tutti.ch" is a trademark of its
> respective owner and is used here only to describe what this tool reads. It reads the same
> public pages a browser does.

Tutti.ch Scraper extracts classified listings from tutti.ch, a Swiss second-hand marketplace, as clean tabular data.

### What does it do?

Searches tutti.ch by keyword across all categories and returns one row per listing: title, a text snippet, price, postcode, place and canton, publication time, category, seller name, thumbnail and a link to the listing. With **Fetch listing detail pages** on, each row also carries the full description, coordinates, all image links, the listing's properties (such as condition) and the seller's account id and member-since date.

### Why use it?

- **Structured columns:** every field is its own column, ready for CSV, Excel or JSON; the price is also given as a number.
- **Detail pages on request:** each search page takes 12 to 62 seconds to fetch (measured 2026-11-08, median about 22 s) and every detail page adds one more fetch, so turn detail pages on only when you need full texts and coordinates.
- **No duplicates:** a listing that shifts between result pages is returned once, also across several keywords.
- **Paid placements flagged:** highlighted listings can have nothing to do with your keyword; they stay in the data with `isHighlighted` set to true, so you can filter them.
- **Public data only:** seller names and the listing details the site shows to anyone are in the rows; nothing from behind a login.

### How to use it

1. Enter one or more keywords in **Search keywords**, for example `iphone`.
2. Set **Maximum rows** and, if you like, **Maximum pages per keyword** (30 listings per page; the site serves at most 101 pages for one search, about 3,030 listings).
3. Optionally switch on **Fetch listing detail pages**.
4. Run the Actor and download the dataset as JSON, CSV, Excel or NDJSON.

#### Input

| Field | Type | Description |
|---|---|---|
| `searchQueries` | array | Search keywords, each searched on its own. |
| `maxItems` | integer | Hard cap on rows across all keywords. |
| `maxPages` | integer | Optional cap on result pages per keyword (1 to 101). |
| `includeDetails` | boolean | Open every listing's page for the full text, coordinates and more. Default off. Each detail page used in a returned row is charged as a separate `detail-page` event. |

#### Sample input

```json
{
    "searchQueries": ["iphone"],
    "maxItems": 10,
    "maxPages": 1,
    "includeDetails": false
}
```

#### Output

Each dataset row contains the fields below. The detail fields are `null` unless **Fetch listing detail pages** is on.

| Field | Type | Description |
|---|---|---|
| `id`, `url` | string | tutti.ch listing id and the listing page. |
| `title`, `descriptionSnippet` | string | Title as published and the start of the text (cut at about 130 characters). |
| `price`, `priceText` | integer / string | Asking price as a whole number and exactly as the site shows it, for example `650.-`. |
| `postcode`, `city`, `canton`, `cantonName` | string | Where the listing is. |
| `postedAt` | string | Publication or last renewal time, ISO 8601. |
| `categoryId` | string | The site's category id. |
| `sellerName`, `sellerLogoUrl`, `sellerBadgeUrl` | string | Seller as shown on the listing; `sellerBadgeUrl` is the image of the seller's subscription badge (seen: plus, pro) and is null for most sellers. |
| `thumbnailUrl`, `imageCount` | string / integer | Thumbnail and number of images. |
| `isHighlighted` | boolean | Paid highlighted placement. |
| `source`, `language` | string | Platform and language code as the site gives them. |
| `description`, `latitude`, `longitude`, `imageUrls`, `properties` | | Detail pages only. |
| `categoryLabel`, `parentCategoryLabel`, `sellerPublicAccountId`, `sellerMemberSince`, `sellerProfileUrl`, `detailPrice` | | Detail pages only. |
| `matchedKeyword`, `searchUrl`, `page`, `scrapedAt` | | Which search returned the row, the page it was on, and when the run started. |

#### Sample output

A real row from a run on 2026-11-08 (keyword `iphone`, detail pages off). The sample leaves out these keys, which every row carries: `description`, `latitude`, `longitude`, `imageUrls`, `properties`, `categoryLabel`, `parentCategoryLabel`, `sellerPublicAccountId`, `sellerMemberSince`, `sellerProfileUrl` and `detailPrice` (all `null` with detail pages off), and `scrapedAt` (the time the run started):

```json
{
    "id": "52369012",
    "url": "https://www.tutti.ch/de/vi/52369012",
    "title": "Ungeöffnetes iPhone 13 Pro Silikon Case mit MagSafe",
    "descriptionSnippet": "Neues Silikon Case mit MagSafe wurde von Apple speziell für dein iPhone 13 Pro entwickelt. Es schützt dein iPhone genauso gut, wi…",
    "price": 55,
    "priceText": "55.-",
    "postcode": "5600",
    "city": "Lenzburg",
    "canton": "AG",
    "cantonName": "Aargau",
    "postedAt": "2026-11-08T08:36:33+01:00",
    "categoryId": "phoneNavigationAccessories",
    "sellerName": "Ariana",
    "sellerLogoUrl": null,
    "sellerBadgeUrl": null,
    "thumbnailUrl": "https://c.tutti.ch/thumbnail/2560818841.jpg",
    "imageCount": 2,
    "isHighlighted": false,
    "source": "tutti",
    "language": "DE",
    "matchedKeyword": "iphone",
    "searchUrl": "https://www.tutti.ch/de/q/suche/Ak6ZpcGhvbmXAlMDAwMA?page=1",
    "page": 1
}
```

### Notes and limits

- One keyword reaches at most 101 pages (about 3,030 listings); that is a limit of the site. Use several specific keywords for larger sweeps.
- The site protects itself against automated access, so each page is fetched through an unblocking service and takes 12 to 62 seconds (measured 2026-11-08, median about 22 s). Detail pages add one fetch per listing.
- Search pages give a text snippet and no coordinates; the full text and coordinates come with detail pages.
- Phone numbers and street addresses are not read.

### Pricing

Detail pages (**Fetch listing detail pages**) are charged as a separate event, one per detail page used in a returned row; a detail page that cannot be read is not charged. For current pricing and details, see the Store page.

### Privacy & Compliance

This Actor reads only publicly accessible pages and returns the public data they show. It sends no login.

# Actor input Schema

## `searchQueries` (type: `array`):

One or more keywords, as typed in the tutti.ch search box, for example "iphone" or "velo". Each keyword is searched on its own across all categories and a listing found twice is returned once.

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

Hard cap on listing rows returned across all keywords.

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

Optional cap on result pages per keyword (30 listings per page). The site serves at most 101 pages for one search, so that is also the ceiling here. Each page is one slow request, 12 to 62 seconds. Without it the run walks every page the site reports, or stops at Maximum rows.

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

Open every listing's own page to add the full description, coordinates, all image links, the listing's properties (such as condition) and the seller's account id and member-since date. Slower: one extra page fetch per listing. Each detail page used in a returned row is charged as a separate pay-per-event "detail-page" event (see the Store page for what it costs); a detail page that cannot be read is not charged.

## Actor input object example

```json
{
  "searchQueries": [
    "iphone"
  ],
  "maxItems": 10,
  "maxPages": 1,
  "includeDetails": false
}
```

# Actor output Schema

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

Classified listings: title, price, place, seller and a link to the listing.

# 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 = {
    "searchQueries": [
        "iphone"
    ],
    "maxItems": 10,
    "maxPages": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("reventadata/tutti-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 = {
    "searchQueries": ["iphone"],
    "maxItems": 10,
    "maxPages": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("reventadata/tutti-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 '{
  "searchQueries": [
    "iphone"
  ],
  "maxItems": 10,
  "maxPages": 1
}' |
apify call reventadata/tutti-scraper --silent --output-dataset

```

## MCP server setup

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