{
  "openapi": "3.0.1",
  "info": {
    "title": "Local.ch $1💰 Listings | URLs | Reviews Scraper",
    "description": "From $1/1K. Pull structured company data from local.ch, including name, address, GPS, phone, email, website, hours, ratings, social links, photos, and categories. Search by category and location or use URLs. Supports Deutsch, English, Français, and Italiano.",
    "version": "0.1",
    "x-build-id": "jkkn1PSbxf4tuPZv0"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/abotapi~local-ch-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-abotapi-local-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~local-ch-scraper/runs": {
      "post": {
        "operationId": "runs-sync-abotapi-local-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~local-ch-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-abotapi-local-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": "Run mode",
            "enum": [
              "search",
              "url"
            ],
            "type": "string",
            "description": "Choose where the actor should start. Search mode builds searches from category and location. URL mode uses pasted local.ch URLs.",
            "default": "search"
          },
          "reviewOnly": {
            "title": "Review-only output",
            "type": "boolean",
            "description": "When ON, output one row per review comment instead of profile listing rows. Works in both search and URL modes.",
            "default": false
          },
          "category": {
            "title": "Category (single)",
            "type": "string",
            "description": "Used in search mode. Examples: `restaurant`, `coiffeur`, `hairdresser`, `dentist`, `garage`, `hotel`.",
            "default": "restaurant"
          },
          "queries": {
            "title": "Categories (multi)",
            "type": "array",
            "description": "Used in search mode. Optional list of categories to search. When set, this overrides the single `category` field.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "where": {
            "title": "Location",
            "type": "string",
            "description": "Used in search mode. Examples: city slug (`zurich`), postal code (`8001`), canton code (`zh`), or `switzerland`.",
            "default": "zurich"
          },
          "locations": {
            "title": "Locations (multi)",
            "type": "array",
            "description": "Search mode only. Optional list of places (same forms as Location: city slug, postal code, canton code, or `switzerland`). When set, every category is searched in every place listed here, and the single Location field is not used. Leave empty to search one Location as before.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "language": {
            "title": "Language",
            "enum": [
              "de",
              "en",
              "fr",
              "it"
            ],
            "type": "string",
            "description": "Used for URLs and page content. local.ch supports German, English, French, and Italian.",
            "default": "de"
          },
          "urls": {
            "title": "local.ch URLs",
            "type": "array",
            "description": "Used in URL mode. Paste one or more local.ch URLs.\n• Search URL (any shape below): walks result pages\n• Profile URL: scrapes one profile page (`/<lang>/d/<city>/<zip>/<category>/<name>-<id>`, the short `/<lang>/d/<id>`, or a profile's own `/<lang>/entries/<id>/vcard` contact-card link)\n\nSupported search shapes: `/<lang>/s/<category>/<location>`, `/<lang>/s/<category>` (nationwide), `/<lang>/q/<location>/<category>` (local.ch's own SEO link form), and `/<lang>/q?what=<category>&where=<location>`. A pasted `sorting=` and local.ch's own filter params (`filter_facet=`, `filter_tags=`, `filter_open=`) are honoured; other filter-like params (`facet=`, `tags=`, `open=`) are not applied by local.ch and are warned about in the log, never silently dropped. Category/city hub pages (`/kategorien/...`, `/staedte/...`) are not search results and are skipped with a warning — paste the `/q/...` link they link out to instead.\n\nWith `reviewOnly=true`, the actor outputs review comments instead of listing rows.\n\nLeaving this field untouched (or omitting it in an API call) in URL mode scrapes a short default list of 12 distinct examples (one per genuinely different shape); `maxListings` is split evenly across them. The short `/d/<id>` profile link and the bare `local.ch` host are also pre-filled as editable examples, but they redirect to a page another default already covers, so they are not in the default list.",
            "default": [
              "https://www.local.ch/de/s/restaurant/zurich",
              "https://www.local.ch/de/s/pizza",
              "https://www.local.ch/de/q/zuerich/restaurant",
              "https://www.local.ch/de/q?what=restaurant&where=zuerich",
              "https://www.local.ch/de/s/restaurant/zurich?page=2",
              "https://www.local.ch/de/s/restaurant/zurich?sorting=rating",
              "https://www.local.ch/de/d/zuerich/8004/restaurant/restaurant-milano-rPs42JlyMK6zuDlCzLm1MA",
              "https://www.local.ch/fr/s/restaurant/geneve",
              "https://www.local.ch/en/q/zurich/restaurant",
              "https://www.local.ch/de/s/restaurant/8001",
              "https://www.local.ch/de/s/restaurant/zh",
              "https://www.local.ch/de/entries/XlOM3DSqSoeHWjGtRZozEw/vcard"
            ],
            "items": {
              "type": "string"
            }
          },
          "maxPages": {
            "title": "Maximum pages per search",
            "minimum": 0,
            "type": "integer",
            "description": "Optional bound on result pages walked per category/keyword/URL. Leave empty to walk every result page."
          },
          "maxListings": {
            "title": "Maximum rows (0 = unlimited)",
            "minimum": 0,
            "type": "integer",
            "description": "Maximum dataset rows to output. Listing rows when `reviewOnly=false`, review rows when `reviewOnly=true`.",
            "default": 20
          },
          "fetchDetails": {
            "title": "Fetch detail pages",
            "type": "boolean",
            "description": "When ON, enrich listing rows with profile-page fields like phone, email, full address, coordinates, opening hours, social links, and attributes.",
            "default": true
          },
          "includeReviews": {
            "title": "Include review comments",
            "type": "boolean",
            "description": "Listing output only. Embed review comments (newest first, up to 'Maximum reviews per listing') inside each listing row. Ignored when `reviewOnly=true`.",
            "default": false
          },
          "maxReviewsPerListing": {
            "title": "Maximum reviews per listing",
            "minimum": 0,
            "maximum": 200,
            "type": "integer",
            "description": "Maximum reviews per business (default 20, up to 200). Listing output: reviews embedded in each listing row when 'Include review comments' is ON; 0 = none. Review-only output: review rows per business; 0 = every review local.ch lists (the 'Maximum review rows' cap still applies). A profile page shows only its 3 newest reviews, so asking for more makes one extra small request per business that has more.",
            "default": 20
          },
          "maxReviewRows": {
            "title": "Maximum review rows",
            "minimum": 0,
            "type": "integer",
            "description": "Review-only output only. Default review-row cap when `reviewOnly=true` and `maxListings=0`.",
            "default": 10
          },
          "maxConcurrency": {
            "title": "Max concurrent detail requests (no longer used)",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "Deprecated and ignored. Detail-fetch concurrency is fixed internally at the reliable default; the input is kept only so saved task inputs keep validating.",
            "default": 6
          },
          "ratedOnly": {
            "title": "Only listings with ratings",
            "type": "boolean",
            "description": "Listing output only. Keep only listing rows that have an average rating.",
            "default": false
          },
          "minRating": {
            "title": "Minimum rating",
            "minimum": 0,
            "maximum": 5,
            "type": "integer",
            "description": "Listing output only. Keep only listing rows at or above this rating."
          },
          "withPhone": {
            "title": "Only listings with a phone number",
            "type": "boolean",
            "description": "Listing output only. Keep only listing rows with a phone number. This data only exists on the profile page, so turning this on also enables Fetch detail pages, regardless of that toggle.",
            "default": false
          },
          "withEmail": {
            "title": "Only listings with an email address",
            "type": "boolean",
            "description": "Listing output only. Keep only listing rows with an email address. This data only exists on the profile page, so turning this on also enables Fetch detail pages, regardless of that toggle.",
            "default": false
          },
          "withWebsite": {
            "title": "Only listings with a website",
            "type": "boolean",
            "description": "Listing output only. Keep only listing rows with a website. This data only exists on the profile page, so turning this on also enables Fetch detail pages, regardless of that toggle.",
            "default": false
          },
          "premiumOnly": {
            "title": "Only premium-listed companies",
            "type": "boolean",
            "description": "Listing output only. Keep only premium-listed profiles. This data only exists on the profile page, so turning this on also enables Fetch detail pages, regardless of that toggle.",
            "default": false
          },
          "matchQueryPostalCode": {
            "title": "Only listings matching the query postal code",
            "type": "boolean",
            "description": "Search mode only. When ON and the location is a postal code (e.g. `8001`), keep only results whose postal code exactly matches the queried one, and skip the rest before any detail-page fetch (saves proxy GB). local.ch's search is not strictly postal-code-bound — when few exact matches exist it pads the page with other regions — so this removes those off-target hits. Default OFF preserves current behaviour.",
            "default": false
          },
          "skipRegionalFallback": {
            "title": "Only listings from exact query matches (no regional fallback)",
            "type": "boolean",
            "description": "Search mode only. When a query has zero exact matches, local.ch silently falls back to a region-wide search and shows unrelated results (e.g. `psychologie` in `1346`). When ON, the actor detects this fallback and emits no output for that query — no detail-page fetch, no rows. Default OFF preserves current behaviour.",
            "default": false
          },
          "sort": {
            "title": "Sort order",
            "enum": [
              "relevance",
              "alphanum",
              "rating",
              "distance"
            ],
            "type": "string",
            "description": "The order local.ch returns search results in (search mode, and pasted search URLs that don't set their own `sorting=`). `relevance` is local.ch's default order. Choosing `alphanum` (A-Z), `rating` (best rated first) or `distance` makes local.ch widen the result set slightly and include some nearby or loosely related businesses: measured for restaurant/Zurich on 2026-10-06, 2,123 results became 2,596. `distance` lists the businesses nearest to the point in 'Sort by distance from' first; the run stops with an error if that point is empty, because local.ch would otherwise return A-Z order.",
            "default": "relevance"
          },
          "distanceFrom": {
            "title": "Sort by distance from (latitude,longitude)",
            "pattern": "^\\s*-?\\d{1,2}(\\.\\d+)?\\s*,\\s*-?\\d{1,3}(\\.\\d+)?\\s*$",
            "type": "string",
            "description": "Only used with Sort order `distance`: the point to measure distance from, as `latitude,longitude` in decimal degrees, e.g. `47.3769,8.5417` (Zurich main station) or `46.9511,7.4386` (Bern). Right-click a spot in any map app to copy its coordinates. A pasted search URL's own `coordinates=` value takes precedence."
          },
          "facetFilters": {
            "title": "local.ch filters (cuisine, diets, amenities, accessibility, ...)",
            "type": "array",
            "description": "The same filters local.ch shows next to its search results, applied by local.ch itself. Type a plain name in any site language or English (`italian`, `Glutenfrei`, `wifi`, `wheelchair_accessible_bathroom`, `open now`) or the exact filter value (`attr_cuisine:italian`, `attr_diets:glutenfree`, `entry:bookable`). Filters from different groups must all match (e.g. Italian AND gluten-free); filters from the same group match any of them (e.g. Italian OR Swiss). Plain names are looked up on the search's first page, which costs one extra page request per search. If a name matches no filter local.ch offers for that search, that search is skipped and an error explains why, so you never get unfiltered results by mistake. Filter params in a pasted URL (`filter_facet=`, `filter_tags=`, `filter_open=`) are applied as well.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "excludeRefusesAdvertising": {
            "title": "Exclude businesses that refuse advertising",
            "type": "boolean",
            "description": "Listing output only. Drop businesses that have marked a phone, mobile or fax number with local.ch's 'no advertising' flag (the asterisk next to the number; output field `refusesAdvertising`). Businesses whose profile page could not be read are also dropped, because their flag is unknown. This flag only exists on the profile page, so turning this on also enables Fetch detail pages.",
            "default": false
          },
          "proxy": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Proxy settings. Swiss residential is recommended — local.ch serves large pages and the shared datacenter pool can stall on them. If residential isn't on your plan, the actor automatically falls back to a datacenter and then a backup pool.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ],
              "apifyProxyCountry": "CH"
            }
          },
          "resumeFromRunId": {
            "title": "Resume from run ID",
            "type": "string",
            "description": "Optional: paste a previous run ID (or dataset ID) from this actor. Rows already collected there are skipped, so this run only returns new ones. Use this after an interrupted run, or to continue a large pull in another run. For recurring monitoring of the same search, use Incremental mode below instead."
          },
          "incrementalMode": {
            "title": "Incremental changes for scheduled runs",
            "type": "boolean",
            "description": "Turn this on for daily or recurring monitoring of the same search/URL/review scope. The first run returns all matching rows as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED rows. Turn on \"Emit unchanged\" or \"Emit expired\" only when you also want those rows returned (and billed). State is kept separately per search/URL/filter/review setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from run ID 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 let the actor derive a key automatically from the search/URL/filter/review setup — different setups then never mix state with each other."
          },
          "emitUnchanged": {
            "title": "Emit unchanged rows (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return rows that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.",
            "default": false
          },
          "emitExpired": {
            "title": "Emit expired rows (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return rows that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max pages/rows capped it or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.",
            "default": false
          },
          "ignoreFieldsForChanges": {
            "title": "Ignore fields when detecting changes (incremental mode only)",
            "type": "array",
            "description": "Output field names that should NOT make a row count as UPDATED. categories/categorySlugs/mainCategory/mainCategorySlug are always ignored, because local.ch's search-result cards return a different (but equally valid) category set on repeated fetches of the same business — the same fields are stable once a profile page is fetched (Fetch detail pages ON, the default). openStatus/opensAt/closesAt (they describe the moment of the fetch) and lastModified (local.ch's own edit timestamp) are always ignored too. Add other field names here (e.g. description, attributes) if changes to them are not relevant to you. 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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}