# German Lawyer Scraper (BRAK) - Email, Phone, beA SAFE-ID (`scrapersdelight/brav-rechtsanwaltsregister-scraper`) Actor

Export admitted German lawyers (Rechtsanwälte) and law-firm companies from the official BRAK register: phone, mobile, email, website, beA SAFE-ID, Kammer, Fachanwalt titles, admission date and branch offices. Whole chambers, cities or postcodes. $0.018 per entry, no start fee.

- **URL**: https://apify.com/scrapersdelight/brav-rechtsanwaltsregister-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$18.00 / 1,000 per register entries

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## German Lawyer Scraper (BRAK) — Email, Phone, beA SAFE-ID

Export admitted German lawyers (**Rechtsanwälte / Rechtsanwältinnen**) and law-firm companies (**Berufsausübungsgesellschaften**) from the official **Bundesweites Amtliches Anwaltsverzeichnis** run by the Bundesrechtsanwaltskammer (BRAK) — with the **phone, mobile, fax, email, website and beA SAFE-ID** each lawyer registered with their chamber, plus Kammer, admission dates, Fachanwalt titles, branch offices and, for firms, their partners and managing organs.

Search a **whole bar chamber** (Berlin: 17,405 entries), a **city**, a **postcode**, a **Fachanwalt title**, a surname or a firm — and get one clean row per register entry, deduplicated by the register's own id.

### Who uses this

- **Legal-tech SaaS** (case management, beA tooling, dictation, billing) — prospect every Kanzlei in a region, segmented by size and Fachanwalt focus.
- **Legal publishers' sales teams** — reach the Fachanwalt für Arbeitsrecht in Munich, or every newly admitted lawyer this year (`admittedSince`).
- **Litigation funders and legal-marketing agencies** — build targeted lists of practising lawyers with a reachable contact.
- **Compliance / KYC** — verify that a counterparty is an admitted lawyer and read their beA SAFE-ID and Kammer from the official register.

### What one row is

One **entry in the register**. Three kinds of entry exist, and every row says which (`recordType`, `isSyndikusRechtsanwalt`):

| Entry | What the row holds |
|---|---|
| Lawyer in private practice | Name, Berufsbezeichnung, Kanzlei, address, contacts, SAFE-ID, Kammer, dates, Fachanwalt titles, branches |
| Syndikusrechtsanwalt (in-house) | The same — but `firmName` is the **employer** (e.g. Deutsche Telekom AG) and the contacts are the employer's work contacts |
| Berufsausübungsgesellschaft (company) | Firm name, legal form, commercial/partnership register entry, seat, contacts, SAFE-ID, **partners (Gesellschafter) and organs**, branches |

The register keeps **one entry per admission**: a lawyer who is both in private practice and employed in-house appears **twice, with two different SAFE-IDs** (verified in the tests). Both are returned; `recordTypes` lets you keep only one kind.

### Contact fill — measured, and it depends on the chamber

Each Kammer records the contact details its lawyers communicate to it, and chambers differ sharply. Measured on 2026-09-22/23 over **complete** result sets (every entry, not a sample), plus the first 600 of Berlin:

| Field | Bonn (city, all 2,655) | Zweibrücken chamber (all 1,431) | Berlin chamber (600 entries, A–) |
|---|---|---|---|
| **email** | **83.6%** | **87.4%** | **86.7%** |
| **phone** | **89.8%** | 9.0% | 80.2% |
| phone or mobile | – | 12.0% | 99.2% |
| mobile | 28.2% | 4.4% | 37.8% |
| fax | 60.1% | 5.2% | 68.3% |
| **website** | 19.2% | 33.8% | 53.7% |
| any of phone / mobile / email / website | – | 88.7% | 99.5% |
| **beA SAFE-ID** | **100%** | **100%** | **100%** |
| street / postcode / city | 100 / 100 / 100% | 100 / 99.3 / 99.3% | 100 / 99.2 / 99.2% |
| Kammer, admission date | 100% | 100% | 100 / 99.8% |
| Fachanwalt title(s) | 23.8% | 34.0% | 17.0% |
| branch offices | 10.6% | 7.1% | 8.2% |

**Email is the dependable channel: 83.6–87.4% across three different frames.** Phone ranges from 9% (Zweibrücken publishes almost none — confirmed in the raw bytes: the Telefon label is present and empty on 149 of 162 sampled entries) to 90% (Bonn). If you need a specific channel, set `requireContact` — entries without it are skipped and **not charged**.

Every null above is genuinely absent in the register, not a parser gap: the test suite checks, over 82 real detail responses captured on the Apify platform, that every published phone, mobile, fax, email, website and SAFE-ID is emitted, and that nothing is emitted where the register shows nothing.

### Input

| Field | What it does |
|---|---|
| `kammern` | Bar chambers to export in full (28 incl. the BGH bar). Berlin 17,405 · Köln 15,519 · Zweibrücken 1,431 entries. |
| `cities` | Towns (Ort). The register matches the Kanzlei **and branch-office** towns, so a Leipzig firm with a Bonn branch appears under Bonn. |
| `postcodes` | German postcodes (PLZ). |
| `specializations` | Fachanwalt titles (all 25 the register offers). |
| `lastName`, `firstName`, `firmName`, `street`, `professionalTitle` | Text filters applied to every search. `firmName` also finds in-house lawyers by employer. |
| `salutation`, `academicTitle`, `publicDefenderOnly` | The register's own filters (Herr/Frau, Dr./Prof., interest in Pflichtverteidigung). |
| `recordTypes` | LAWYER / SYNDIKUS / COMPANY. Filtered on the result card **before** any detail is read. |
| `requireContact` | Deliver only entries with any contact, an email, a phone or a website. Skipped entries are free. |
| `admittedSince` | Only entries admitted on or after a date (YYYY-MM-DD) — newly admitted lawyers and new firms. |
| `includeBranches`, `includePartners`, `includeOtherTabs` | Branch offices; Gesellschafter/Organe; Zustellungsbevollmächtigte, Abwickler, Vertretung. On by default; each costs one extra request only for entries that have one. |
| `maxResults` | Cap on delivered (= charged) entries. Default 20. |
| `maxConcurrency` | Parallel register sessions (1–8, default 4). |
| `proxyConfiguration` | Default: none — the register has no anti-bot measures. |

The lists combine as a product: `kammern: ["M"]` × `specializations: ["1","5"]` = two searches (Arbeitsrecht in München, Familienrecht in München); a lawyer holding both titles is returned once.

Example — every Fachanwalt für Arbeitsrecht in Hamburg who publishes an email:

```json
{ "kammern": ["HH"], "specializations": ["1"], "requireContact": "email", "maxResults": 2000 }
```

### Output

| Field | Description |
|---|---|
| `id` | The beA SAFE-ID — the register's own unique id. Rows are deduplicated on it before charging. |
| `recordType` | `LAWYER` or `COMPANY`. |
| `fullName` | Name as the register prints it (with academic titles), or the firm name. |
| `salutation`, `academicTitle`, `firstName`, `lastName`, `familyName` | Name parts. `firstName`/`academicTitle` are set only when the result card and the detail agree on the split — otherwise null, never guessed. `familyName` only where the register prints a separate Familienname. |
| `professionalTitle`, `professionalTitles` | Berufsbezeichnung, e.g. Rechtsanwältin, Rechtsanwalt (Syndikusrechtsanwalt), Avukat (Türkei), § 206 BRAO. |
| `isSyndikusRechtsanwalt`, `isForeignLawyer` | In-house lawyer; European / § 206 BRAO lawyer. |
| `fachanwaltTitles` | Fachanwalt titles, e.g. `["Arbeitsrecht","Strafrecht"]`. |
| `firmName`, `legalForm` | Kanzlei name (Syndikus: employer); legal form for companies. |
| `phone`, `mobile`, `fax` | Exactly as published (`0228-28629220`), never reformatted. |
| `email`, `emailRaw` | `email` only when it is a well-formed address; the published value is always in `emailRaw` (one entry publishes `sabina .krispenz@basf.com` — kept raw, never repaired). |
| `website` | Internetadresse as published. |
| `beaSafeId` | The besonderes elektronisches Anwaltspostfach SAFE-ID. |
| `addressLines`, `addressExtra`, `street`, `postcode`, `city`, `country` | Kanzlei address, split; the raw lines are kept. |
| `chamber` | Kammerzugehörigkeit. |
| `admissionDate(Iso)`, `firstAdmissionDate(Iso)` | Datum der (ersten) Zulassung, as published and as YYYY-MM-DD. |
| `publicDefenderInterest` | Ja / Keine Angabe. |
| `registeredSeat`, `commercialRegister` | Companies: seat / Hauptniederlassung and Handels- or Partnerschaftsregister entry. |
| `partners`, `organs` | Companies: Gesellschafter and Organe, `{name, role, extra}`. |
| `branchCount`, `branches` | Every further Kanzlei / Zweigstelle / Zweigniederlassung with its own address, phone, mobile, fax, email, website. |
| `deliveryAgents`, `liquidators`, `representatives` | Zustellungsbevollmächtigte, Abwickler (practice being wound up), chamber-appointed Vertretung — each a person with address and contacts. |
| `otherFields` | Any detail line this actor does not map, verbatim — nothing the register publishes is dropped. |
| `contactChannels` | How many of phone / mobile / email / website are present (0–4). |
| `cardText`, `searchQuery`, `source`, `sourceUrl`, `scrapedAt` | Audit trail. |

#### A real row

From live run `TBYr0lFFj02pJ2t9j` (`{"kammern":["B"],"maxResults":600}`, 2026-09-23). A company entry is shown; three of its four branch offices are cut for length.

```json
{
  "id": "DE.BRAK.b63f3dd3-da0a-42ed-9441-0bd40dd962af.7f80",
  "recordType": "COMPANY",
  "fullName": "Anwaltskanzlei Heckmann Rechtsanwaltsgesellschaft mbH",
  "professionalTitle": "Berufsausübungsgesellschaft",
  "isSyndikusRechtsanwalt": false,
  "firmName": "Anwaltskanzlei Heckmann Rechtsanwaltsgesellschaft mbH",
  "legalForm": "Gesellschaft mit beschränkter Haftung",
  "phone": "030-40504030",
  "mobile": null,
  "fax": "030-405040321",
  "email": "ra@heckmann.net",
  "website": "www.heckmann.net",
  "beaSafeId": "DE.BRAK.b63f3dd3-da0a-42ed-9441-0bd40dd962af.7f80",
  "street": "Kantstraße 22",
  "postcode": "10623",
  "city": "Berlin",
  "country": "DE",
  "chamber": "Berlin",
  "admissionDate": "22.01.2026",
  "admissionDateIso": "2026-01-22",
  "registeredSeat": "Deutschland",
  "commercialRegister": "Handelsregister Abteilung B - 281620 B - Berlin",
  "partners": [{ "name": "Jan A. Heckmann", "role": "Rechtsanwalt", "extra": null }],
  "organs": [{ "name": "Jan A. Heckmann", "role": "Rechtsanwalt", "extra": null }],
  "branchCount": 4,
  "branches": [
    { "type": "Zweigstelle", "name": "Anwaltskanzlei Heckmann Rechtsanwaltsgesellschaft mbH", "street": "Alter Markt 8",
      "postcode": "18439", "city": "Stralsund", "country": "DE", "chamber": "Berlin",
      "phone": "03831-4348130", "email": "ra@heckmann.net", "website": "www.heckmann.net", "beaSafeId": null }
  ],
  "contactChannels": 3,
  "source": "Bundesweites Amtliches Anwaltsverzeichnis (BRAK)",
  "sourceUrl": "https://bravsearch.bea-brak.de/bravsearch/index.brak"
}
```

### Completeness guarantees

The register only holds the first 100 rows of a result until a "load all" step runs, and then **silently repeats rows 96–99** for every later page — a scraper that does not check this ships the same 4–6 people hundreds of times. This actor:

- asserts that **every page is the page it asked for** (first row index = requested index), and retries on a fresh session if not;
- asserts that **every detail belongs to its card** — surname, the card's "PLZ Ort" line and its Berufsbezeichnung must match. (Measured: when the register cannot serve a detail it returns the *previous* entry's dialog with HTTP 200. The name check alone would pass that for the two entries of one person; address + title catch it.);
- deduplicates on the SAFE-ID **before charging**;
- for any search it reads to the end, checks that **every row index 0…total−1 was examined** and fails the run loudly if not. Full reads of Bonn (2,655 / 2,655 unique SAFE-IDs) and the Zweibrücken chamber (1,431 / 1,431) passed with zero duplicates;
- writes a `RUN_SUMMARY` with the register's own total per search, pages read, coverage, per-field fill, and three separate kinds of "nothing": searches with no hits, entries skipped by your filters (free), and entries unreachable after retries (free).

### Pricing

**$0.018 per delivered register entry.** No start fee, no charge for skipped, duplicate or unreadable entries. A full Zweibrücken chamber (1,431 entries) costs $25.76; 600 Berlin entries cost $10.80. Set `maxResults` (or a run-level maximum charge) to cap spend.

### Speed

About **5 entries per second** with the default 4 sessions (600 Berlin entries in 112 s on the platform). A full large chamber (17,405 for Berlin) takes roughly an hour: raise `maxConcurrency` to 8 and the run timeout for whole-chamber exports.

### Data limits

- **Contact fill is the chamber's, not ours** — see the table. Zweibrücken publishes phone for ~9% of lawyers; email is the reliable channel there.
- `firstName` is withheld (null) where the result card and the detail disagree on how to split a name — 1 of 1,382 person rows in the full Zweibrücken read, 0 of 560 in the Berlin run. `fullName` and `lastName` are still there.
- The register publishes no language skills or Tätigkeitsschwerpunkte on this search surface, so neither is returned.
- A lawyer with no Berufsbezeichnung line in the register (24 of 2,655 in Bonn — in-house lawyers with an employer but no title printed) comes back with `professionalTitle: null`; that is what the register shows.
- The register can change while a long run is in progress; if its total changes between sessions the search is flagged and not claimed complete.

### Source

**Bundesweites Amtliches Anwaltsverzeichnis**, `https://bravsearch.bea-brak.de/bravsearch/index.brak` (the search behind rechtsanwaltsregister.org), maintained by the Bundesrechtsanwaltskammer (BRAK).

**Is this an official BRAK tool?** No. This actor is independent and not affiliated with the BRAK or any Rechtsanwaltskammer. It reads only data that is publicly available in the official register.

# Actor input Schema

## `kammern` (type: `array`):

Export every entry of one or more regional bar chambers. Each chamber is searched separately. Chambers are big (Berlin 17,405 entries, Köln 15,519, Zweibrücken 1,431 on 2026-09-22), so raise maxResults to take a whole chamber. Combined with cities or postcodes, every chamber x city pair is one search.

## `cities` (type: `array`):

Towns as the register spells them, e.g. Bonn, München, Köln, Frankfurt. The register matches the Kanzlei town and branch-office towns. Each city is searched separately.

## `postcodes` (type: `array`):

German postcodes, e.g. 10117 or 80331. Each postcode is searched separately.

## `specializations` (type: `array`):

Only lawyers holding this Fachanwalt title. Each title is searched separately (so a lawyer with two selected titles is returned once — rows are deduplicated by beA SAFE-ID).

## `lastName` (type: `string`):

The lawyer's surname, as the register's own name field matches it. Applied to every search.

## `firstName` (type: `string`):

The lawyer's first name. Applied to every search.

## `firmName` (type: `string`):

Law-firm name, Berufsausübungsgesellschaft name or (for in-house Syndikusrechtsanwälte) the employer, e.g. Deutsche Telekom AG. Applied to every search.

## `street` (type: `string`):

Street of the Kanzlei address. Applied to every search.

## `professionalTitle` (type: `string`):

Professional title text, e.g. Rechtsanwalt, Rechtsanwältin, or a foreign title such as Solicitor or Avocat for European lawyers established in Germany.

## `salutation` (type: `string`):

Only entries with this salutation.

## `academicTitle` (type: `string`):

Only entries holding this academic title.

## `publicDefenderOnly` (type: `boolean`):

Only lawyers who told their chamber they want to be appointed as court-appointed defence counsel.

## `recordTypes` (type: `array`):

LAWYER = a lawyer in private practice. SYNDIKUS = an in-house lawyer (Syndikusrechtsanwalt) — the Kanzlei on the row is the EMPLOYER. COMPANY = a Berufsausübungsgesellschaft (law-firm company, e.g. a PartG mbB or Rechtsanwaltsgesellschaft mbH). Filtered from the result card before any detail is fetched. Empty = all three.

## `requireContact` (type: `string`):

Deliver (and charge for) only entries that publish this contact. Entries without it are skipped free of charge and counted in RUN_SUMMARY. Contact fill depends heavily on the chamber — see the README.

## `admittedSince` (type: `string`):

Only entries whose current admission date (Datum der Zulassung) is on or after this date — newly admitted lawyers and newly registered firms. Every entry in scope is still read, so set a narrow scope for speed.

## `includeBranches` (type: `boolean`):

Add every further Kanzlei, Zweigstelle and Zweigniederlassung with its own address, phone, mobile, fax, email and website. One extra request only for entries that have branches.

## `includePartners` (type: `boolean`):

For a Berufsausübungsgesellschaft, add its Gesellschafter and Organe (named partners / managing directors). One extra request per company.

## `includeOtherTabs` (type: `boolean`):

Add the Zustellungsbevollmächtigte, Abwickler and Vertretung tabs where the register shows them (rare; an Abwickler means the practice is being wound up).

## `maxResults` (type: `integer`):

Stop after this many delivered entries (you are charged per delivered entry). A whole chamber needs up to 17,405.

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

How many independent register sessions read pages in parallel. Each reads about 2 entries per second.

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

Default: no proxy — the register has no anti-bot measures and answers Apify's own egress directly. If you choose a proxy, each parallel session keeps ONE sticky proxy IP for its whole conversation (the register's session is bound to it).

## `saveRawResponses` (type: `integer`):

Store the first N raw register responses in the key-value store (RAW\_\*) for troubleshooting. 0 = off.

## Actor input object example

```json
{
  "cities": [
    "Bonn"
  ],
  "publicDefenderOnly": false,
  "requireContact": "none",
  "includeBranches": true,
  "includePartners": true,
  "includeOtherTabs": true,
  "maxResults": 10,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "saveRawResponses": 0
}
```

# Actor output Schema

## `entries` (type: `string`):

One row per admitted Rechtsanwalt / Rechtsanwältin or Berufsausübungsgesellschaft: phone, mobile, fax, email, website, beA SAFE-ID, address, Kammer, admission dates, Fachanwalt titles, branch offices, partners.

## `summary` (type: `string`):

Per search: the register's own total, pages read, index coverage and completeness verdict; per-field fill of examined and delivered rows; what was skipped and why (free); request counts.

# 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 = {
    "cities": [
        "Bonn"
    ],
    "maxResults": 10,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/brav-rechtsanwaltsregister-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 = {
    "cities": ["Bonn"],
    "maxResults": 10,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/brav-rechtsanwaltsregister-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 '{
  "cities": [
    "Bonn"
  ],
  "maxResults": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scrapersdelight/brav-rechtsanwaltsregister-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/brav-rechtsanwaltsregister-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/v9eSBYhK5q5fuei4t/builds/ZX2TxEDF3ARI978Id/openapi.json
