# ZVG-Portal Scraper — German Judicial Property Auctions (`studio-amba/zvg-portal-scraper`) Actor

Extract forced-auction (Zwangsversteigerung) property listings from ZVG-Portal.de, the official German court-auction calendar shared by the Bund and all 16 Länder. Get case numbers, courts, property addresses, market value (Verkehrswert), auction dates, and official notice/appraisal PDFs.

- **URL**: https://apify.com/studio-amba/zvg-portal-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 result scrapeds

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/platform/actors/running/actors-in-store#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

## ZVG-Portal Scraper

Extract forced-auction (Zwangsversteigerung) property listings from [ZVG-Portal.de](https://www.zvg-portal.de) — the official German court-auction calendar operated on behalf of the Bund and all German Länder — into structured JSON with case numbers, courts, addresses, market value, and auction dates.

### What is ZVG-Portal Scraper?

**ZVG-Portal Scraper** pulls structured data out of Germany's official statutory forced-auction publication portal, helping you find distressed/below-market properties, track auction volume by state and court, and automate court-auction lead generation — without manually browsing hundreds of Amtsgericht listings.

- **Find distressed-property leads:** every listing is a court-ordered Zwangsversteigerung under German civil procedure — debt-recovery and foreclosure sales, published because courts are legally required to disclose them
- **Monitor auction activity by state or court:** filter by Bundesland or a single Amtsgericht and track how many properties go to auction, the spread of market values, and which courts are most active
- **Automate market research:** export case numbers, market values (Verkehrswert), and auction dates to build datasets for valuation models or regional distress analysis
- **Get the official documents:** every active listing includes links to the official notice (amtliche Bekanntmachung) and, where published, the court appraisal (Gutachten) and info sheet (Exposee) PDFs
- **Build automated alerts:** schedule daily runs and get notified when new lots matching your criteria (state, price range) appear

ZVG-Portal.de has **no public API and no export feature**. This scraper queries the same search form the website itself uses and parses the resulting HTML — no browser automation needed, it's a plain server-rendered government site.

### How to scrape ZVG Portal data

The input is simple: pick a Bundesland (or "alle" for all states), optionally a court and a market-value range, and run.

#### Input options

| Field | Type | Required | Description |
|-------|------|----------|--------------|
| `bundesland` | String | No | 2-letter German state code, e.g. `"nw"` (Nordrhein-Westfalen), `"by"` (Bayern), `"be"` (Berlin). Default: `"nw"`. Use `"alle"` to loop over all 14 states that publish through this portal |
| `gerichtId` | Integer | No | Filter to a single Amtsgericht by its internal court ID. Default `0` = every court in the selected state(s) |
| `vonWert` | Integer | No | Minimum Verkehrswert (market value, EUR) |
| `bisWert` | Integer | No | Maximum Verkehrswert (market value, EUR) |
| `includeCancelled` | Boolean | No | Include auctions marked "wurde aufgehoben" (cancelled). Default `false` — these carry no address, value, or detail link and aren't useful leads |
| `scrapeDetails` | Boolean | No | Fetch each listing's detail page for the real court name, full description, Grundbuch, venue address, and PDF links (default `true`). The search results list alone doesn't expose the court name |
| `maxResults` | Integer | No | Maximum active listings to return (default: 200, max: 5000) |
| `proxyConfiguration` | Object | No | Apify proxy settings. The site has no anti-bot protection (verified) — not required for success |

#### Tips for best results

- **Start with a single state:** `bundesland: "nw"` (Nordrhein-Westfalen) is the largest single-state dataset on the portal, currently ~950 active listings
- **Use `"alle"` for full national coverage:** loops all 14 active states in one run (Hamburg and Mecklenburg-Vorpommern always return 0 — they publish through a separate regional system and are skipped automatically)
- **Use `vonWert`/`bisWert` for lead qualification:** e.g. `vonWert: 50000, bisWert: 200000` to target a specific investment band
- **Leave `scrapeDetails: true`:** the court name (Amtsgericht) is only available from the detail page — the search list only shows the state
- **Schedule daily runs:** new lots are published continuously across Germany's Amtsgerichte

### Output

Results are stored in a **dataset** you can download as JSON, CSV, Excel, XML, or HTML directly from the Apify Console.

#### Output fields

| Field | Type | Example |
|-------|------|---------|
| `title` | String | `"Einfamilienhaus — Bad Salzuflen"` |
| `price` | Number | null | `226000` (Verkehrswert / market value, EUR) |
| `currency` | String | `"EUR"` |
| `listingType` | String | `"auction"` (always — every listing is a court-ordered forced sale) |
| `propertyType` | String | `"einfamilienhaus"` |
| `address` | String | `"Schillerstraße 11, 32105 Bad Salzuflen, Bad Salzuflen"` |
| `city` | String | `"Bad Salzuflen"` |
| `postalCode` | String | `"32105"` |
| `province` | String | `"nw"` (2-letter Bundesland code) |
| `court` | String | `"Lemgo"` (competent Amtsgericht, only when `scrapeDetails: true`) |
| `procedureNumber` | String | `"0013 K 0042/2025"` (Aktenzeichen) |
| `auctionType` | String | `"Zwangsversteigerung zum Zwecke der Aufhebung der Gemeinschaft"` |
| `auctionDate` | String (ISO) | `"2026-08-12T09:00:00"` |
| `auctionDateRaw` | String | `"Mittwoch, 12. August 2026, 09:00 Uhr"` (original German format) |
| `landRegister` | String | `"Bad Salzuflen Blatt 5664"` (Grundbuch) |
| `venueAddress` | String | `"Amtsgericht Lemgo, Hauptgebäude, Am Lindenhaus 2, 32657 Lemgo, Sitzungssaal 102"` |
| `courtWebsite` | String | Court's own website, when published |
| `geoServerUrl` / `googleMapsUrl` | String | Map links for the property address |
| `description` | String | Full free-text property description (Beschreibung) |
| `pdfUrls` | Array | `[{ "label": "Gutachten", "url": "..." }, ...]` — official notice, appraisal, info sheet, photos |
| `isCancelled` | Boolean | `true` if the auction was cancelled (`wurde aufgehoben`) |
| `lastUpdated` | String | Site's own "letzte Aktualisierung" timestamp |
| `url` | String | Detail page URL |
| `scrapedAt` | String (ISO) | Scrape timestamp |

#### Example output

```json
{
    "title": "unbebautes Grundstück — Inden",
    "price": 8300,
    "currency": "EUR",
    "url": "https://www.zvg-portal.de/index.php?button=showZvg&zvg_id=167766&land_abk=nw",
    "scrapedAt": "2026-08-11T19:48:13.411Z",
    "listingType": "auction",
    "province": "nw",
    "procedureNumber": "0007 K 0021/2025",
    "isCancelled": false,
    "propertyType": "unbebautes grundstück",
    "address": "Frenzer Driesch, 52459 Inden, Frenz",
    "city": "Inden",
    "postalCode": "52459",
    "auctionDate": "2026-08-12T08:45:00",
    "auctionDateRaw": "Mittwoch, 12. August 2026, 08:45 Uhr",
    "court": "Jülich",
    "auctionType": "Zwangsversteigerung zum Zwecke der Aufhebung der Gemeinschaft",
    "landRegister": "Frenz Blatt 100",
    "description": "Unbebautes Grundstück im bauplanungsrechtlichen Außenbereich in Inden-Frenz",
    "venueAddress": "Amtsgericht Jülich, Hauptgebäude, Wilhelmstr. 15, 52428 Jülich, Etage 1., Sitzungssaal 1.06",
    "courtWebsite": "http://www.ag-juelich.nrw.de",
    "pdfUrls": [
        { "label": "Gutachten", "url": "https://www.zvg-portal.de/index.php?button=showAnhang&land_abk=nw&file_id=376389&zvg_id=167766" },
        { "label": "amtliche Bekanntmachung", "url": "https://www.zvg-portal.de/index.php?button=showAnhang&land_abk=nw&file_id=376388&zvg_id=167766" }
    ]
}
```

### How much does it cost to scrape ZVG-Portal?

ZVG-Portal Scraper uses **only HTTP requests** (no browser), making it fast and cheap to run.

| Scenario | Est. cost | Time |
|----------|-----------|------|
| 20 listings with full details | ~$0.11 | ~10 sec |
| 200 listings with full details (one state) | ~$1.01 | ~1-2 min |
| All 14 states, 1000+ listings (`bundesland: "alle"`) | ~$5+ | several minutes |

**Pricing breakdown:**

- Per run start: $0.01
- Per result: $0.005

Usage cost only settles once a run reports **SUCCEEDED** — reading the dataset mid-run will undercount what you'll actually be charged.

### Can I integrate this with other apps?

Yes, through [Apify integrations](https://apify.com/integrations):

- **Google Sheets** — automatically export auction leads to a spreadsheet
- **Slack / Email** — get notified when new lots match your criteria
- **Zapier / Make** — trigger workflows when a run finishes
- **Airtable** — build a searchable auction-property database
- **REST API** — call the scraper programmatically from any language

### Can I use this as an API?

Yes. Use the [Apify API](https://docs.apify.com/api/v2) to run it programmatically.

**Python:**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("studio-amba/zvg-portal-scraper").call(run_input={
    "bundesland": "nw",
    "vonWert": 50000,
    "bisWert": 300000,
    "maxResults": 200,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"{item['title']} — €{item['price']:,}" if item.get("price") else item["title"])
```

**JavaScript:**

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

const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });
const run = await client.actor('studio-amba/zvg-portal-scraper').call({
    bundesland: 'nw',
    vonWert: 50000,
    bisWert: 300000,
    maxResults: 200,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

Check the [API tab](https://apify.com/studio-amba/zvg-portal-scraper/api) for full documentation.

### FAQ

#### What is ZVG-Portal.de?

ZVG-Portal.de (Zwangsversteigerungstermine) is the official German court-auction publication portal, operated on behalf of the Bund and the Länder as part of the shared Justizportal infrastructure. Every listing is a court-ordered forced sale (Zwangsversteigerung) — properties sold under German civil procedure, typically as part of debt-recovery or foreclosure proceedings, at values a court-appointed appraiser has set as the Verkehrswert.

#### How does this scraper work?

It submits the same search form the website's own "Termine suchen" page uses (with `&all=1` to get the full unbounded result set in one request, not paginated), then fetches each active listing's detail page for the court name, description, and official document links. No browser automation needed — it's a plain server-rendered site.

#### Can I filter by market value?

Yes. `vonWert` and `bisWert` filter directly on the source's Verkehrswert field.

#### Why do some listings have `price: null`?

A small minority of courts paste a rich, Word-formatted breakdown into the market-value field instead of a single figure (nested tables, multiple sub-totals like "Lfd. Nr. 1" / "Lfd. Nr. 2", or "davon entfällt auf Zubehör" accessory components). Rather than guess which numbers to sum — real risk of double-counting a sub-component — this scraper leaves `price` as `null` for those and extracts everything else normally. The figure is still visible on the source site's detail page.

#### Is it legal to scrape ZVG-Portal.de?

The underlying data (forced-auction notices) is a statutory public disclosure under German ZVG law — courts are legally required to publish it. The site's `robots.txt` does disallow the specific detail-page and attachment endpoints this scraper uses, which is a real (if purely technical, not legal-prose) signal worth being aware of. As with any scraping tool, use the data responsibly and in compliance with applicable laws; this scraper does not access anything not already shown to any visitor of the public site.

### Limitations

- **Germany only**, covering the 14 states that publish through this specific portal. Hamburg and Mecklenburg-Vorpommern use a separate regional system and always return 0 results here.
- **Court name requires `scrapeDetails: true`.** The search results list only states the Bundesland, not the Amtsgericht — the real court name is only on the detail page.
- **A minority of listings have no clean market value.** See the FAQ above — some courts format the Verkehrswert field as a rich breakdown rather than a plain figure; those come through with `price: null` rather than a guessed number.
- **Cancelled auctions carry almost no data.** By default (`includeCancelled: false`) they're excluded — a cancelled listing has no address, value, court, or detail link, just the case number and cancellation notice.
- **PDFs are linked, not downloaded.** `pdfUrls` gives you the official notice/appraisal/info-sheet links; this scraper doesn't fetch or parse the PDF contents themselves.

### Related scrapers

Combine with these for broader European judicial-auction and real estate coverage:

- 🇮🇹 [Astegiudiziarie Scraper](https://apify.com/studio-amba/astegiudiziarie-scraper) — Italian judicial property auctions (same auction genre)
- 🇧🇪 [Biddit Scraper](https://apify.com/studio-amba/biddit-scraper) — Belgian notarial property auctions
- 🏚️ [Distressed Property Feed](https://apify.com/studio-amba/distressed-property-feed) — cross-source distressed-property leads
- 🇩🇪 [Bundesanzeiger Scraper](https://apify.com/studio-amba/bundesanzeiger-scraper) — German official company-registry disclosures

### Your feedback

Found a bug or have a feature request? Open an issue on the [Issues tab](https://apify.com/studio-amba/zvg-portal-scraper/issues). We actively maintain this scraper and respond to all reports.

# Actor input Schema

## `bundesland` (type: `string`):

2-letter German state code, e.g. "nw" (Nordrhein-Westfalen), "by" (Bayern), "be" (Berlin). Use "alle" to loop over all 14 states that publish through this portal (Hamburg "hh" and Mecklenburg-Vorpommern "mv" always return 0 — they use a separate regional system and are skipped automatically in "alle" mode).

## `gerichtId` (type: `integer`):

Filter to a single Amtsgericht by its internal ger\_id. Leave at 0 (default) to search every court in the selected state(s).

## `vonWert` (type: `integer`):

Only return lots with a Verkehrswert (market value) at or above this amount.

## `bisWert` (type: `integer`):

Only return lots with a Verkehrswert (market value) at or below this amount.

## `includeCancelled` (type: `boolean`):

Include auctions marked "wurde aufgehoben" (cancelled). These carry no address, value, or detail link — not useful as leads. Default: skip them.

## `scrapeDetails` (type: `boolean`):

Fetch each listing's detail page for the real court (Amtsgericht) name, full description, land register (Grundbuch), auction venue address, and links to the official notice/appraisal PDFs. Adds one HTTP request per listing. Recommended — the search results list alone doesn't expose the court name.

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

Maximum number of active (non-cancelled) auction listings to return.

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

ZVG-Portal.de has no anti-bot protection (verified) — proxy is not required for success, but recommended for large multi-state runs to be a good citizen.

## Actor input object example

```json
{
  "bundesland": "nw",
  "gerichtId": 0,
  "includeCancelled": false,
  "scrapeDetails": true,
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "bundesland": "nw",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/zvg-portal-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 = {
    "bundesland": "nw",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/zvg-portal-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 '{
  "bundesland": "nw",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call studio-amba/zvg-portal-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/zvg-portal-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/1IUkCQGcfSo7X9mp3/builds/9lJGRVeqmapjdMY8P/openapi.json
