# Das Telefonbuch Scraper - German Business Leads & New Listings (`neverempty/das-telefonbuch-scraper`) Actor

For B2B lead lists and local sales teams: German businesses from Das Telefonbuch by category or company name and city, district or postcode, with name, address, phone in +49 format, website, category and rating. Monitor returns only newly listed businesses. Businesses only, no private persons.

- **URL**: https://apify.com/neverempty/das-telefonbuch-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 business returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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

## Das Telefonbuch Scraper - German Business Leads & New Listings

Get **German business listings from Das Telefonbuch (dastelefonbuch.de)** as clean JSON: business name, categories (Branchen), street, postcode, city and district, phone number as shown and in **+49 (E.164) format**, website and its domain, rating, and whether it is a paid premium listing. Search by **category** (Zahnarzt, Rechtsanwalt, Friseur, Steuerberater, Restaurant ...) or by **company name**, in any German **city, district or postcode**. Turn on **Monitor mode** and scheduled runs return **only businesses that are newly listed** since the last check - new practices, new shops, new offices - so a lead list, CRM or local sales team gets the new ones without paying for the same businesses again.

- **Businesses only - no private persons.** This Actor uses the directory's business search (Branchen) and company search (Firmen). It never searches people's names, and every row is checked: a listing that Das Telefonbuch does not mark as a business is never returned.
- **New listings monitor.** `onlyNew` reads the whole list of each search and returns only businesses that were not on it at the previous check (`changeType: new`). Runs where nothing is new return one free row that says so.
- **Honest about the 500 limit.** Das Telefonbuch shows at most 500 businesses for one search (50 pages of 10). When a list is bigger, the free row says so and tells you to search by district (`Berlin-Charlottenburg`) or postcode (`10707`). A monitor never pretends to watch a list it cannot see completely.
- **Phone numbers ready to dial.** `phone` is the number exactly as the directory shows it (`030 8 64 73 20`); `phoneE164` is the same number as `+49308647320`.
- **No charge when the directory cannot be read.** Pages that could not be read, a place or category the directory does not know, and a monitor list that could not be read completely come back as free rows that say why.
- **Several searches in one run.** Categories and company names times locations, up to 10 searches per run.
- **Fast.** One page is 10 businesses: production runs on 2026-09-24 took 4-8 s for 17-50 businesses and 5-7 s for a monitor check of a 17-business list, at 256 MB.

Unofficial. Reads the public Das Telefonbuch business listing pages (`/Branchen/...` and `/Firmen/...`), the same pages a person sees without logging in. It does not open private (residential) listings, does not decode protected e-mail addresses, and does not solve or bypass check pages. Only publicly listed business information is returned.

### What you get

One row per business. Example (a production run on 2026-09-24, category `Zahnarzt` in `10707`):

```json
{
  "status": "ok",
  "changeType": null,
  "businessName": "KU64 Dr. Ziegler & Partner",
  "categories": ["Zahnärzte"],
  "keywords": ["Zahnarzt Berlin", "Zahnärzte", "Zahnklinik Berlin", "Zahnmedizin Berlin"],
  "street": "Kurfürstendamm 64",
  "postalCode": "10707",
  "city": "Berlin",
  "district": "Charlottenburg",
  "address": "Kurfürstendamm 64, 10707 Berlin",
  "phone": "030 8 64 73 20",
  "phoneE164": "+49308647320",
  "website": "http://ku64.de",
  "websiteDomain": "ku64.de",
  "rating": 3.5,
  "ratingCount": 51,
  "isPremiumListing": true,
  "listingId": "0001131496977",
  "detailUrl": "https://adresse.dastelefonbuch.de/Berlin/1-Zahnarzt-KU64-Dr-Ziegler-Partner-Berlin-Kurf%C3%BCrstendamm.html",
  "positionInSearch": 1,
  "listingsInSearch": 17,
  "listingsInSearchIsLowerBound": false,
  "directoryHeading": "Zahnarzt in 10707",
  "searchType": "category",
  "searchWhat": "Zahnarzt",
  "searchWhere": "10707",
  "searchUrl": "https://www.dastelefonbuch.de/Branchen/Zahnarzt/10707",
  "watchName": null,
  "checkedAt": "2026-09-24T12:18:59.952Z"
}
```

Checked against the same Das Telefonbuch pages in a browser after the runs: 15 of 15 businesses from three production runs had the same name, street, postcode, website and the same phone number on screen.

| Column | Meaning |
|---|---|
| `status` | `ok` for a business row. Other values are free rows that say why nothing (or not everything) was returned (below) |
| `changeType` | Monitor mode only: `first-check` (first run of this watch) or `new` (not on the list at the previous check). Null when monitor mode is off |
| `businessName` | Name as listed |
| `categories`, `keywords` | The directory's categories (Branche) and keywords (Stichworte) for the business, when listed |
| `street`, `postalCode`, `city`, `district`, `address` | Address as listed; `address` joins street, postcode and city |
| `phone`, `phoneE164` | Phone as shown, and the same number in international format (+49...). Null when the listing has no phone |
| `website`, `websiteDomain` | Website link as listed, and its domain without `www.` |
| `rating`, `ratingCount` | Average rating (1-5) and number of ratings shown on the listing; null when there are none |
| `isPremiumListing` | True when the listing is a paid premium entry |
| `listingId`, `detailUrl` | The directory's listing ID and its detail page |
| `positionInSearch` | Position in the list of this search (1 = first) |
| `listingsInSearch`, `listingsInSearchIsLowerBound` | Number of businesses the directory reports for this search. For 1,000 or more it only says "> 999": then `listingsInSearch` is null and `listingsInSearchIsLowerBound` is true |
| `directoryHeading` | The heading of the result page, for example `Zahnarzt in 10707` |
| `searchType`, `searchWhat`, `searchWhere`, `searchUrl` | The search this row came from |
| `note` | Free rows only: why nothing (or not everything) was returned |
| `watchName`, `checkedAt` | The watch name you gave, and the time of the check |

#### Free rows (not charged)

| `status` | When |
|---|---|
| `no-results` | The directory lists no business for this search |
| `not-found` | The directory does not know this category, company name or place (check the German spelling, for example `München`) |
| `no-new-listings` | Monitor mode: nothing on the list is new since the previous check |
| `more-not-returned` | More businesses than `maxResultsPerSearch`, or more than the 500 the directory shows for one search; says how many and what to do |
| `too-broad-to-watch` | Monitor mode: the list has more than 500 businesses, so it cannot be watched completely. Nothing is charged or remembered; watch districts or postcodes instead |
| `not-checked` | Monitor mode: the run already read 60 pages; put this search in its own run |
| `incomplete` | Some pages of the list could not be read; the businesses before them were returned, the rest were not |
| `unreadable` | The directory could not be read, even after asking again from other IP addresses. In monitor mode also when only part of the list could be read (nothing is judged new from a partial list) |
| `blocked` | The directory showed a check page. This Actor does not solve or bypass check pages; it stops and does not run the remaining searches |
| `budget-reached` | The run hit the maximum total charge you set. In monitor mode the businesses not returned are not remembered, so the next run returns them |
| `bad-input` | The input could not be used |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `categories` | list of strings | - | Categories or keywords in German: `Zahnarzt`, `Rechtsanwalt`, `Friseur`, `Steuerberater`, `Restaurant`, `Physiotherapie` ... Leave Categories, Company names and Locations all empty to run the example `Zahnarzt` in `Berlin` |
| `companyNames` | list of strings | - | Company names to look up, for example `Siemens` |
| `locations` | list of strings | - | Cities, districts or postcodes: `Berlin`, `Berlin-Charlottenburg`, `Frankfurt am Main`, `München`, `10707` |
| `maxResultsPerSearch` | integer 1-500 | 50 | Most businesses returned for one search. In monitor mode: most new businesses per search in one run (the rest come next run) |
| `onlyNew` | boolean | false | Monitor mode: return only businesses that are new on the list since the previous check |
| `watchName` | string | - | Separate memories for monitor mode, for example one per client |
| `resetMonitoringState` | boolean | false | Forget what this watch has seen, so the run is a first check again |

Each category and each company name is searched in each location (up to 10 searches per run).

### Examples

Dentists in two districts, 100 each:

```json
{ "categories": ["Zahnarzt"], "locations": ["Berlin-Charlottenburg", "Berlin-Wilmersdorf"], "maxResultsPerSearch": 100 }
```

New law firms and tax advisers in a postcode area, every morning (schedule this input):

```json
{ "categories": ["Rechtsanwalt", "Steuerberater"], "locations": ["60311"], "onlyNew": true, "watchName": "frankfurt-leads" }
```

A company in a city:

```json
{ "companyNames": ["Siemens"], "locations": ["München"] }
```

### Pricing

Pay per event:

- **Run start** - once per run that read the directory: before the first business row is returned, or, in monitor mode, when a whole list was read (also when nothing is new - that pays for the check). Not charged when nothing could be read, the place or category is unknown, or the list is too big to watch.
- **Business returned** - per business row.

Free rows are never charged. If you set a maximum total charge for a run, the run stops before it would go over it and says so; a run whose maximum has no room for the start fee plus one business does not request anything.

### Monitor mode, step by step

1. First run: reads the whole list of each search (up to 500 businesses), returns up to `maxResultsPerSearch` as `first-check`, and remembers every business on the list as the starting point.
2. Later runs: read the whole list again and return only businesses that were not on it before, as `new`.
3. If a page of a list cannot be read, that list is not judged at all in that run (nothing returned, remembered or charged); the next run checks it again.

Keep one schedule per search and watch name. Two overlapping runs of the same watch can both return the same new business (the memory is a key-value store without transactions).

### Limits

- Das Telefonbuch shows at most 500 businesses for one search. Use districts or postcodes for bigger lists.
- Very deep pages of a big list (page 40 and later) are slow on the directory's side and sometimes time out (HTTP 504) even after asking again. Then the run returns what it read and an `incomplete` row says from which page on nothing was returned (in a production run on 2026-09-24, 430 of 500 for dentists in Berlin). Smaller areas avoid deep pages.
- Only the fields shown on the result list are returned. E-mail addresses (protected on the directory's detail pages) and opening hours are not returned.
- Private (residential) listings are never returned.
- Each search is its own list: a business that appears in two of your searches (for example a postcode and the city around it) comes back once for each search. Use `listingId` to remove duplicates.

### Support

Questions, a field you need, or a search that does not work: open an issue on the Issues tab of this Actor.

# Actor input Schema

## `categories` (type: `array`):

Business categories or keywords in German, as you would type them on dastelefonbuch.de, for example Zahnarzt, Rechtsanwalt, Friseur, Steuerberater, Restaurant. Each category is searched in each location. Leave Categories, Company names and Locations all empty to run the example search Zahnarzt in Berlin.

## `companyNames` (type: `array`):

Optional. Company names to look up in each location, for example Siemens or Abacus. Uses the directory's company search, which lists businesses only.

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

German cities, districts or 5-digit postcodes, for example Berlin, Berlin-Charlottenburg, Frankfurt am Main, München, 10707. Categories and company names times locations make the searches of a run (up to 10 per run).

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

Most businesses returned for one search (one category or company name in one location). Das Telefonbuch shows at most 500 for one search; for bigger lists use districts or postcodes. Empty = 50. In monitor mode it limits the new businesses returned per search in one run; the rest come in the next run.

## `onlyNew` (type: `boolean`):

On: every run reads the whole list of each search and returns only businesses that were not on it at the previous check (the first run returns up to Max businesses per search and remembers the whole list as the starting point). Runs where nothing is new return a free row saying so. A search with more than 500 businesses cannot be watched completely and returns a free row asking for a smaller area. Off = return the list as it is now.

## `watchName` (type: `string`):

Optional. Keeps separate memories for monitor mode, for example one per client (letters, digits, dot, dash, underscore; up to 40). Runs with the same watch name and the same search share what has already been returned.

## `resetMonitoringState` (type: `boolean`):

On: forget what this watch has seen for these searches before running, so this run is a first check again.

## Actor input object example

```json
{
  "categories": [
    "Zahnarzt"
  ],
  "locations": [
    "Berlin"
  ]
}
```

# Actor output Schema

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

One row per business: name, categories, street, postcode, city, district, phone as shown and in +49 (E.164) format, website and its domain, rating, premium listing flag, listing ID and detail page link, and its position in the search. Monitor mode marks rows first-check or new. A search with no businesses, nothing new, a place the directory does not know, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# 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 = {
    "categories": [
        "Zahnarzt"
    ],
    "locations": [
        "Berlin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/das-telefonbuch-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 = {
    "categories": ["Zahnarzt"],
    "locations": ["Berlin"],
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/das-telefonbuch-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 '{
  "categories": [
    "Zahnarzt"
  ],
  "locations": [
    "Berlin"
  ]
}' |
apify call neverempty/das-telefonbuch-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/das-telefonbuch-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/cNrG2UtQiPTJpUdVr/builds/45fK1t4S2bzS2Iv5v/openapi.json
