{
  "openapi": "3.0.1",
  "info": {
    "title": "Tutti.ch Scraper - Listings, Prices, Sellers & Details",
    "description": "Scrape listings from tutti.ch across all 23 categories, including vehicles, property, electronics, furniture, fashion, and jobs. Search by keyword, category, city, or use tutti.ch URLs. Returns price, location, canton, seller details, images, and optional GPS, phone, photos, and attributes.",
    "version": "1.0",
    "x-build-id": "MRNd6XLcDA9n7ijsP"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/abotapi~tutti-ch-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-abotapi-tutti-ch-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/abotapi~tutti-ch-scraper/runs": {
      "post": {
        "operationId": "runs-sync-abotapi-tutti-ch-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/abotapi~tutti-ch-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-abotapi-tutti-ch-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "title": "Mode",
            "enum": [
              "search",
              "url"
            ],
            "type": "string",
            "description": "How to start the scrape. 'search' builds listing URLs from a keyword, category and locations. 'url' takes tutti.ch search or category URLs you paste.",
            "default": "search"
          },
          "keyword": {
            "title": "Keyword",
            "type": "string",
            "description": "Free-text search term, e.g. 'velo', 'iphone', 'wohnung'. Leave empty to browse a whole category or the entire marketplace. Search mode only."
          },
          "category": {
            "title": "Category",
            "enum": [
              "all",
              "realEstate",
              "vehicles",
              "clothesAccessories",
              "household",
              "phonesNavigation",
              "computersAccessories",
              "tvAudio",
              "sportsOutdoors",
              "babyChild",
              "toysHandicrafts",
              "gardenCraft",
              "animals",
              "antiquesArts",
              "books",
              "music",
              "films",
              "photoVideo",
              "collectibles",
              "officeBusiness",
              "services",
              "jobs",
              "ticketsVouchers",
              "other"
            ],
            "type": "string",
            "description": "Restrict the search to one tutti.ch category. 'All categories' searches the entire marketplace. Search mode only.",
            "default": "all"
          },
          "locations": {
            "title": "Locations (city or canton names)",
            "type": "array",
            "description": "Swiss cities or cantons by name, e.g. 'Zürich', 'Genève', 'Bern', 'Basel', 'Lausanne', 'Luzern', 'Winterthur', or 'Kanton Wallis'. German, French, Italian and English spellings are accepted. A location that returns no listings is reported as an error in the log. Leave empty to search all of Switzerland. For precise or complex locations, use URL mode instead. Search mode only.",
            "items": {
              "type": "string"
            }
          },
          "minPrice": {
            "title": "Min price (CHF)",
            "minimum": 0,
            "type": "integer",
            "description": "Only keep listings priced at or above this amount (applied to the numeric price). Listings with no price are kept. Search mode only: results from a pasted URL in URL mode are not price filtered."
          },
          "maxPrice": {
            "title": "Max price (CHF)",
            "minimum": 0,
            "type": "integer",
            "description": "Only keep listings priced at or below this amount (applied to the numeric price). Listings with no price are kept. Search mode only: results from a pasted URL in URL mode are not price filtered."
          },
          "urls": {
            "title": "tutti.ch URLs",
            "type": "array",
            "description": "Paste tutti.ch links: search pages (/de/q/..., /fr/q/..., /it/q/..., or /q/... without a language) and single listing pages (/de/vi/...). Pagination (?page=N) and sorting (?sorting=) in a pasted search link are honoured, apex and mobile hosts are normalised, and listing links are scraped as one listing each. Seller pages (/seller?id=...) and category landing pages (/de/cat/...) are not supported and are skipped with a warning; paste a /q/ search link instead. The search-mode fields above (keyword, category, locations, min price, max price) are ignored here, and so is Site language, because a pasted URL carries its own. The Sorting field below does still apply: it sets the ordering used from the page after the pasted one onward. Leaving this field untouched in URL mode scrapes one example of each distinct supported link shape (language-less and apex/mobile links normalise to one of them). URL mode only.",
            "default": [
              "https://www.tutti.ch/de/q/marktplatz/Ak8CrgIWcA",
              "https://www.tutti.ch/de/q/velos/Ak8CoYmljeWNsZXOUwMDAwA",
              "https://www.tutti.ch/de/q/velos-fahrraeder/Ak8CoYmljeWNsZXOUwMDAwA?page=2&sorting=cheapest",
              "https://www.tutti.ch/de/q/velos-bern/Ak8CoYmljeWNsZXOUwMDAkZOobG9jYXRpb26vZ2VvLWNhbnRvbi1iZXJuwA",
              "https://www.tutti.ch/fr/q/velos/Ak8CoYmljeWNsZXOUwMDAwA",
              "https://www.tutti.ch/de/q",
              "https://www.tutti.ch/de/vi/83641798",
              "https://www.tutti.ch/de/vi/zuerich/oberstrass/sport-outdoor/velos/price-citybike/80329196",
              "https://www.tutti.ch/it/q/velos/Ak8CoYmljeWNsZXOUwMDAwA",
              "https://www.tutti.ch/fr/vi/80442365",
              "https://www.tutti.ch/de/vi/specialized-hotrock/83638829"
            ],
            "items": {
              "type": "string"
            }
          },
          "sortBy": {
            "title": "Sort by",
            "enum": [
              "newest",
              "oldest",
              "price_asc",
              "price_desc",
              "relevant"
            ],
            "type": "string",
            "description": "Result ordering. tutti.ch defaults to newest first. Applies in BOTH search mode and URL mode. In URL mode the page you pasted is fetched exactly as given, and every following result page is then requested with this ordering, so pick the value that matches your pasted URL if you want its own sorting kept.",
            "default": "newest"
          },
          "language": {
            "title": "Site language",
            "enum": [
              "de",
              "fr",
              "it"
            ],
            "type": "string",
            "description": "Which tutti.ch language edition to scrape (affects URL path and localized slugs). Search mode only: in URL mode the language is read from the pasted URL and from the page itself, so this field is ignored there.",
            "default": "de"
          },
          "fetchDetails": {
            "title": "Fetch full details (extra cost)",
            "type": "boolean",
            "description": "Adds GPS coordinates, ALL image URLs, seller account ID, member-since date, phone (when published), street address and structured properties. Costs one extra request per listing.",
            "default": false
          },
          "maxPages": {
            "title": "Max pages per search",
            "minimum": 0,
            "type": "integer",
            "description": "How many result pages to walk per search/URL (30 listings per page). Leave empty to walk every result page (tutti.ch itself stops serving results around page 100 = 3000 listings per search). A category search in search mode with a listing cap walks at least 40 pages regardless of this value, because the category filter runs client-side."
          },
          "maxAgeHours": {
            "title": "Max listing age (hours)",
            "minimum": 0,
            "type": "integer",
            "description": "Only keep listings posted within the last N hours. 0 or empty = no age limit. With sort = Newest first the walk stops as soon as a whole page is older than the limit."
          },
          "maxListings": {
            "title": "Max listings (0 = no limit)",
            "minimum": 0,
            "maximum": 100000,
            "type": "integer",
            "description": "Hard cap on total listings across all searches. 0 means no limit (still bounded by maxPages). Maximum 100000.",
            "default": 20
          },
          "proxy": {
            "title": "Proxy",
            "type": "object",
            "description": "tutti.ch is reachable from Swiss residential IPs. Residential proxy with country = CH is strongly recommended. Datacenter and free-tier connections are usually rejected by the upstream edge.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ],
              "apifyProxyCountry": "CH"
            }
          },
          "maxResidentialPercent": {
            "title": "Residential usage cap (%)",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "Soft cap on how much of the run may use residential proxy. Once exceeded, the actor automatically downgrades to the cheaper rotating backup pool for remaining requests. 100 = never downgrade on this signal.",
            "default": 100
          },
          "residentialBudgetMb": {
            "title": "Residential traffic budget (MB)",
            "minimum": 0,
            "type": "integer",
            "description": "Approximate residential bandwidth budget for the run, in megabytes. Once the estimated residential transfer exceeds this, the actor downgrades to the cheaper backup pool. 0 = no budget limit.",
            "default": 0
          },
          "resumeFromRunId": {
            "title": "Resume from a previous run (optional)",
            "type": "string",
            "description": "Paste a previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by listing id) are skipped, so this run only saves the new ones. Leave empty for a normal fresh run."
          },
          "incrementalMode": {
            "title": "Incremental changes for scheduled runs",
            "type": "boolean",
            "description": "Turn this on for daily or recurring monitoring of the same search. The first run returns every matching listing as NEW. Later runs return only NEW, UPDATED and REAPPEARED listings; unchanged listings are neither returned nor billed. Every returned row gets changeType, changedFields, firstSeenAt and lastSeenAt. A listing counts as UPDATED when any field other than these ignored ones changed: scraped_at, timestamp, highlighted (the posted/renewed time and the promoted flag change without any content change). Everything else counts, for example price, title, description, images, seller, location, properties, so a changed price or description returns the listing as UPDATED with the field names in changedFields. Ignored fields are still returned in each row. Add your own with the ignore list below. State is kept per search (mode, keyword, category, locations, price range, language, detail setting, URLs), never per item limit, page limit or maximum listing age. Use State key to name a campaign. To continue one interrupted run instead, use Resume from a previous run above.",
            "default": false
          },
          "stateKey": {
            "title": "State key (optional, incremental mode only)",
            "type": "string",
            "description": "Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to derive a key from the search settings, so different searches never mix state."
          },
          "emitUnchanged": {
            "title": "Emit unchanged listings (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return listings that have not changed since the last run, marked UNCHANGED. These rows are delivered and billed like any other row, so leave it off unless you want the full snapshot every run.",
            "default": false
          },
          "emitExpired": {
            "title": "Emit expired listings (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return listings seen in a previous run that are no longer found, marked EXPIRED. Only produced after a run that read the whole search to its last page: not when Max listings or Max pages stopped it, not for a pasted single listing or a link that starts after page 1, not with a maximum listing age, not with Resume, and not when a page or detail page failed. These rows are delivered and billed like any other row, and they count toward Max listings: any beyond it are reported on a later run.",
            "default": false
          },
          "ignoreFieldsForChanges": {
            "title": "Ignore fields when detecting changes (incremental mode only)",
            "type": "array",
            "description": "Output field names that should NOT make a listing count as UPDATED, even when they changed. Always ignored: scraped_at, timestamp, highlighted (listed here so you know; you do not need to add them). A name without a dot matches that field at any nesting depth (for example text inside properties); a dotted name matches from the top of the row. Ignored fields are still returned in the output.",
            "items": {
              "type": "string"
            }
          },
          "mcpConnectors": {
            "title": "Pipe results into your apps (optional)",
            "type": "array",
            "description": "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": {
            "title": "Notion parent page (Notion connector only)",
            "type": "string",
            "description": "URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors."
          },
          "maxNotifyListings": {
            "title": "Max items to export per connector",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Cap on items written to each connector per run. Does not affect the dataset.",
            "default": 50
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}