# Local.ch Swiss Business Leads Scraper (`swissdatatools/local-ch-swiss-business-leads`) Actor

Find Swiss businesses by keyword and location. Export clean, deduplicated leads with phones, emails, websites, addresses, contactability metrics and AI-ready summaries for CRM, sales and automation.

- **URL**: https://apify.com/swissdatatools/local-ch-swiss-business-leads.md
- **Developed by:** [Matis Monnin](https://apify.com/swissdatatools) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 valid swiss business leads

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Local.ch Swiss Business Leads

Find Swiss businesses by category and location and export clean, deduplicated leads with phone numbers, emails, websites, addresses, contactability metrics, and AI-ready summaries.

You do not need to build a local.ch URL. Enter a keyword such as `plumber` and a place such as `Lausanne`, then start the run.

**Find Swiss businesses and get clean, deduplicated, CRM-ready and AI-ready leads.**

### Why use this Actor?

- Built for Switzerland: Lausanne, Geneva, Zürich, Bern, Basel, Lugano, Vaud, Valais, Ticino and other cities or cantons
- Simple keyword + location search, including several keywords and locations in one run
- Designed for business listings: skips entries that local.ch explicitly marks as `PERSON`. This is not a guaranteed private-person filter; review `localChUrl` before outreach
- Global deduplication across searches, with `matchedSearches` kept on the surviving row
- CRM-ready fields: E.164 phones, normalized emails/websites, full address, canton, country
- Deterministic contactability and completeness scores — not a commercial lead score
- Deterministic `ai_summary` Markdown, with no external LLM
- Advertising preference signal when local.ch publishes it (`refusesAdvertising`)
- French, German, Italian and English search pages
- Pay Per Event is designed to charge only for valid dataset rows

### Try it in 30 seconds

```json
{
  "searchTerms": ["plumber"],
  "locations": ["Lausanne"],
  "maxResultsPerSearch": 100
}
```

Start the Actor. Open the default dataset when the run finishes. If nothing is found, the log explains: `No businesses found. Try a broader category or location.`

### What data do you get?

| Field | Description |
| --- | --- |
| `name` | Business name |
| `category`, `categories` | Main and additional categories |
| `street`, `postalCode`, `city`, `canton`, `fullAddress` | Address parts plus a combined address |
| `country` | Always `Switzerland` when a record is saved |
| `phone`, `normalizedPhone` | Original phone and E.164 when conversion is certain |
| `email`, `normalizedEmail` | Original email and lowercase email |
| `website`, `normalizedWebsite` | Original website and normalized URL |
| `facebook`, `instagram`, `linkedin`, `otherSocialLinks` | Public social links when requested |
| `rating`, `reviewCount` | Public rating summary when present |
| `latitude`, `longitude` | Coordinates when present |
| `openingHours` | Structured hours when detail crawl is enabled |
| `refusesAdvertising` | Source signal, or `null` if unknown |
| `contactabilityScore` | 0–100 channel availability score |
| `dataCompleteness` | 0–100 share of essential fields present |
| `localChUrl` | Public local.ch profile URL for verification |
| `matchedSearches` | Which keyword/location pairs found this business |
| `ai_summary` | Deterministic Markdown summary |

Missing values stay `null`. The Actor does not invent data.

### Example output

```json
{
  "name": "Example Sanitär GmbH",
  "category": "Plumbing",
  "city": "Lausanne",
  "canton": "VD",
  "country": "Switzerland",
  "fullAddress": "Rue Example 12, 1000 Lausanne, VD",
  "phone": "021 000 00 00",
  "normalizedPhone": "+41210000000",
  "email": "info@example.ch",
  "normalizedEmail": "info@example.ch",
  "website": "www.example.ch",
  "normalizedWebsite": "https://www.example.ch",
  "contactabilityScore": 95,
  "dataCompleteness": 100,
  "refusesAdvertising": null,
  "localChUrl": "https://www.local.ch/fr/d/lausanne/1000/sanitaire/example-sanitar-gmbh-abcdefghijklmnopqrs",
  "matchedSearches": [{ "query": "plumber", "location": "Lausanne" }],
  "ai_summary": "## Example Sanitär GmbH\n\nSwiss business located in Lausanne, VD.\n\n- Category: Plumbing\n- Address: Rue Example 12, 1000 Lausanne, VD\n- Phone: 021 000 00 00\n- Email: info@example.ch\n- Website: www.example.ch\n- Contactability: 95/100",
  "source": "local.ch"
}
```

### CRM-ready leads

Use `normalizedPhone`, `normalizedEmail`, `normalizedWebsite` and `fullAddress` for HubSpot, Salesforce, Google Sheets or a CRM import.

`contactabilityScore` only answers: **how many useful contact channels are available?**

- normalized email: +35
- normalized phone: +30
- normalized website: +20
- full address: +10
- at least one social link: +5
- maximum: 100

It is not a conversion score, solvency score or company-value score.

`dataCompleteness` is the percentage of these essential fields that are present: name, category, fullAddress, phone, email, website, canton, coordinates.

### AI-ready leads

`ai_summary` is generated locally from fields that actually exist. No LLM API is called. Enable or disable it with `includeAiSummary`. The summary language follows the selected local.ch language.

### Multiple searches in one run

```json
{
  "searchTerms": ["plumber", "electrician"],
  "locations": ["Lausanne", "Geneva"]
}
```

This queues four combinations:

- plumber / Lausanne
- plumber / Geneva
- electrician / Lausanne
- electrician / Geneva

Set `maxResultsPerSearch` per combination and `maxTotalResults` for the whole run. When `deduplicateResults` is true, one business appears once, with every matching search stored in `matchedSearches`.

### Switzerland coverage

Works with Swiss cities, cantons, regions and postal-code style locations, including Lausanne, Geneva, Zürich, Bern, Basel, Lugano, Vaud, Valais and Ticino. Language `auto` picks French, German or Italian from the location; you can force `fr`, `de`, `it` or `en`.

### How much does it cost?

This Actor uses Pay Per Event pricing.

- **$1.50 per 1,000 valid business leads**
- Equivalent to **$0.0015 per valid `business-result`**
- Duplicates, invalid rows, empty searches and failed requests are not billed

Platform usage is included in the Actor price.

Examples:

- 100 valid leads: **$0.15**
- 1,000 valid leads: **$1.50**
- 5,000 valid leads: **$7.50**
- 10,000 valid leads: **$15.00**

Apify Proxy standard datacenter is enabled by default for reliable access to Local.ch.

### API

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('USERNAME/local-ch-swiss-business-leads').call({
  searchTerms: ['plumber'],
  locations: ['Lausanne'],
  maxResultsPerSearch: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient('YOUR_TOKEN')
run = client.actor('USERNAME/local-ch-swiss-business-leads').call(run_input={
    'searchTerms': ['plumber'],
    'locations': ['Lausanne'],
    'maxResultsPerSearch': 100,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

curl:

```bash
curl "https://api.apify.com/v2/acts/USERNAME~local-ch-swiss-business-leads/runs?token=YOUR_TOKEN" ^
  -X POST -H "Content-Type: application/json" ^
  -d "{\"searchTerms\":[\"plumber\"],\"locations\":[\"Lausanne\"],\"maxResultsPerSearch\":100}"
```

Replace `USERNAME` with your Apify account name after publishing.

### Integrations

Use the Apify API, webhooks, or Apify integrations such as n8n, Make and Zapier where Apify provides a connector. MCP / agent users can read the default dataset and the `RUN_SUMMARY` key-value record.

### FAQ

#### How to scrape Local.ch?

Provide `searchTerms` and `locations`. The Actor builds local.ch search URLs, paginates from the public result count, and writes one dataset row per business.

#### How to get Swiss business leads?

Run a keyword such as `dentist`, `fiduciary` or `restaurant` with a Swiss city or canton. Export JSON, CSV or Excel from the dataset.

#### Can I export Local.ch businesses to Excel?

Yes. Download the default dataset as CSV or Excel from Apify Console.

#### Can I get email addresses from Local.ch?

Yes, when the public listing includes an email. Coverage varies by category and by result page. Missing emails stay `null`.

#### Can I search businesses by canton?

Yes. Use a canton name or code in `locations`, for example `Vaud`, `Ticino` or `VD`.

#### Does it collect private persons?

The Actor is designed for business listings. When local.ch publishes an entry type such as `PERSON`, that row is skipped. If the type is missing, the Actor does not guess from the name. This has not been proven on a mixed phone-book SERP, so it is not a guarantee that every row is a company. Review `localChUrl` before outreach.

#### How much does it cost to scrape Local.ch?

The Actor costs **$1.50 per 1,000 valid business leads**.

You are charged only for valid `business-result` rows written to the dataset. Duplicates, invalid rows, empty searches and failed requests are not billed.

#### Can I use it with AI agents?

Yes. Each row includes `ai_summary`, `localChUrl` and a run summary at key `RUN_SUMMARY`. No LLM is required to produce the summary.

### Responsible use

This Actor collects public business information from local.ch. You are responsible for complying with applicable laws, local.ch terms, and your own outreach rules. `refusesAdvertising` is a source-published signal, not a legal guarantee. This README is not legal advice.

### Advanced options

- `includeDetails`: visit profile pages for descriptions and opening hours
- `includeOpeningHours`: fill `openingHours` from profile JSON-LD
- `includeSocialLinks`: keep Facebook, Instagram, LinkedIn and other public social URLs
- `maxConcurrency` / `requestDelayMs`: keep traffic polite (defaults 5 and 400 ms)
- `proxyConfiguration`: Apify Proxy **standard datacenter is on by default**. Without it, Local.ch returns an HTTP 200 anti-bot page with no listings. Use residential groups only if standard proxy stays blocked.

### Support

Use the Issues tab on the Actor page.

# Actor input Schema

## `searchTerms` (type: `array`):

Enter one or more business types or keywords, such as plumber, dentist, restaurant, fiduciary, or electrician.

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

Enter Swiss cities, cantons, regions, or postal codes such as Lausanne, Zürich, Geneva, VD, Ticino, or 1000 Lausanne.

## `maxResultsPerSearch` (type: `integer`):

Maximum number of businesses to keep for each keyword × location combination.

## `maxTotalResults` (type: `integer`):

Hard cap for the whole run, after deduplication.

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

Language used for local.ch search pages and AI summaries. Auto picks French, German or Italian from the location.

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

Visit business profiles to collect descriptions and opening hours. Search pages already include phone, email, website and address for most listings.

## `includeAiSummary` (type: `boolean`):

Add a deterministic Markdown summary (ai\_summary) to each lead. No external LLM is used.

## `deduplicateResults` (type: `boolean`):

Keep one row per business across all searches and merge matchedSearches.

## `includeOpeningHours` (type: `boolean`):

Collect structured opening hours from business profiles. Requires visiting detail pages.

## `includeSocialLinks` (type: `boolean`):

Collect Facebook, Instagram, LinkedIn and other public social links when local.ch publishes them.

## `maxConcurrency` (type: `integer`):

How many pages to fetch in parallel. Keep this low to reduce blocking and platform cost.

## `requestDelayMs` (type: `integer`):

Wait this many milliseconds before each request. Helps stay polite toward local.ch.

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

Apify Proxy standard (datacenter) is required on the Apify platform. Local.ch otherwise returns an HTTP 200 anti-bot page with no listings. Leave the default on. Use residential groups only if standard proxy still gets blocked. Do not use this to bypass CAPTCHAs.

## Actor input object example

```json
{
  "searchTerms": [
    "restaurant"
  ],
  "locations": [
    "Lausanne"
  ],
  "maxResultsPerSearch": 100,
  "maxTotalResults": 1000,
  "language": "auto",
  "includeDetails": false,
  "includeAiSummary": true,
  "deduplicateResults": true,
  "includeOpeningHours": false,
  "includeSocialLinks": false,
  "maxConcurrency": 5,
  "requestDelayMs": 400,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `businesses` (type: `string`):

Deduplicated CRM-ready and AI-ready local.ch businesses saved during this run.

## `runSummary` (type: `string`):

Counts for searches, unique businesses, coverage and errors. Stored as key RUN\_SUMMARY.

# 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 = {
    "searchTerms": [
        "restaurant"
    ],
    "locations": [
        "Lausanne"
    ],
    "maxResultsPerSearch": 100,
    "maxTotalResults": 1000,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("swissdatatools/local-ch-swiss-business-leads").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 = {
    "searchTerms": ["restaurant"],
    "locations": ["Lausanne"],
    "maxResultsPerSearch": 100,
    "maxTotalResults": 1000,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("swissdatatools/local-ch-swiss-business-leads").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 '{
  "searchTerms": [
    "restaurant"
  ],
  "locations": [
    "Lausanne"
  ],
  "maxResultsPerSearch": 100,
  "maxTotalResults": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call swissdatatools/local-ch-swiss-business-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,swissdatatools/local-ch-swiss-business-leads"
        }
    }
}

```

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/xVYyPNrj04o8jnO9x/builds/TJAT9ouqN4iqNxmxH/openapi.json
