# mobile.de Scraper: Car Prices, Price Rating & New Listings (`moggedhq/mobile-de-scraper`) Actor

Get mobile.de car listings for BMW, VW or Audi with price rating, mileage, registration, specs, dealer and location. Track new listings, price drops and observed price history. $0.0015 per listing, $0.0015 extra for details, $0.002 start. No login. Car listings Germany data.

- **URL**: https://apify.com/moggedhq/mobile-de-scraper.md
- **Developed by:** [mogged](https://apify.com/moggedhq) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 listings

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

## mobile.de Scraper: Car Prices, Price Rating & New Listings

Independent of mobile.de. For car dealers, exporters and vehicle price analysts who monitor public mobile.de inventory every day. Get asking prices, mobile.de's own price rating, mileage, registration, technical specifications, dealer reputation and location in a consistent dataset.

A basic listing costs about **$0.0015**, or **$0.003 with full details**, including the basic listing fee. Each run also has a **$0.002 start charge** at the default memory. Twenty basic listings cost about **$0.032**; twenty with full specifications and equipment cost about **$0.062**. Tier discounts reduce listing fees. No login, account cookies or separate data subscription is required.

### What you get

Each car is a separate row, with a readable `verdict` and typed fields for spreadsheets, dashboards and AI workflows. One listing found through overlapping searches appears once and has one listing fee. A separate `SUMMARY` record reports counts, the median asking price, skipped watch records and any unavailable inputs.

| Field group | Included data |
| --- | --- |
| Identity | `listingId`, `title`, `make`, `model`, `variant`, `url` |
| Current price | `price`, `currency`, `priceRating`, `priceRatingCode`, `marketPriceRange` |
| Monitoring | `previousPrice`, `previousCurrency`, `priceChange`, `priceChangePercent`, `isNewSinceLastRun`, `firstSeenAt`, `scrapedAt` |
| Observed history | `priceHistory`, `observationCount`, `firstObservedPrice`, `lowestObservedPrice`, `highestObservedPrice`, `totalPriceChange`, `priceDropCount`, `lastPriceChangeAt`, `daysObserved`, `daysSincePriceChange` |
| Advertised reductions | `advertisedPreviousPrice`, `advertisedPriceReduction`, when mobile.de shows a crossed-out price |
| Vehicle basics | `firstRegistration`, `mileageKm`, `fuel`, `powerKw`, `powerHp`, `powerHpUnit`, `transmission`, `bodyType` |
| Vehicle condition | `colour`, `doors`, `seats`, `owners`, `accidentFree`, `vatDeductible` |
| Seller and place | `sellerType`, `dealerName`, `dealerId`, `dealerRating`, `dealerReviewCount`, `city`, `postcode`, `country` |
| Full details | `features`, `specifications`, `netPrice`, `vatRate`, plus the complete photo list where available |
| Business contact | Published business phone, street address, homepage and coordinates when the source supplies them |
| Provenance | `sourceUrl`, `detailsIncluded` and observation timestamps |

Missing values are `null`. Accident history stays unknown without an explicit statement. Doors retain values such as `4/5`; registration retains the source month/year. Prices on request remain `null`, rather than zero. Private seller names, phones, addresses and personal contact profiles are excluded.

#### Example from a real cloud run

Observed on 28 October 2026:

```json
{
  "verdict": "BMW 320 e M SPORT 18ZOLL FACELIFT SPURHALTE,LED,NAVI: €25,480, Sehr guter Preis, first seen by this watch.",
  "listingId": "49248474879040",
  "price": 25480,
  "currency": "EUR",
  "priceRating": "Sehr guter Preis",
  "firstRegistration": "11/2022",
  "mileageKm": 73334,
  "powerKw": 150,
  "dealerName": "Autohaus Royal GmbH",
  "city": "Ludwigsfelde",
  "postcode": "14974"
}
```

### Start in three steps

1. Enter a make and model, for example BMW and 320, or Volkswagen and Golf. You can instead paste search, category, dealer inventory or individual listing URLs into `startUrls`.
2. Set your limits and optional location. Use a German postcode and a radius in kilometres. Leave `includeDetails` off for quick price monitoring; turn it on for equipment and the full specification sheet.
3. Start the run, inspect the overview, and export JSON, CSV or Excel. Read `SUMMARY` when a query returns no cars or a source page is temporarily unavailable.

The prefill requests twenty BMW 320 listings near 10115 Berlin. A real cloud test on 28 October 2026 returned twenty rows in nine seconds. Normal runs use public JSON without a browser.

### Input reference

| Input | Meaning and example |
| --- | --- |
| `startUrls` | Optional array of mobile.de search, category, listing or public dealer URLs. Use one URL string per entry. At most 100 URLs. |
| `make` | Manufacturer, such as `BMW`, `Volkswagen`, `Audi`, `Mercedes-Benz` or `Toyota`. Used when no URLs are supplied. |
| `model` | Model or family, such as `320`, `Golf`, `A4` or `C-Klasse`. Requires a make. |
| `priceMin` / `priceMax` | Optional minimum and maximum asking price in EUR. |
| `yearFrom` | Earliest first-registration year. This does not mean manufacture year. |
| `mileageMax` | Maximum odometer reading in kilometres. |
| `fuel` | Empty for any fuel, or `petrol`, `diesel`, `electric`, `hybrid`, `lpg`, `cng`, `hydrogen`. |
| `postcode` | Optional five-digit German postcode. Unknown codes produce a free note, without broadening the search. |
| `radiusKm` | Radius around the postcode centre, from 5 to 500. Default 100. |
| `sellerType` | `any`, `dealer` or `private`. |
| `maxItems` | Maximum distinct listings delivered, from 1 to 10,000. In only-new mode it also caps inspected listings. Default 20. |
| `includeDetails` | Fetch full specifications and equipment. Default `false`. The optional detail fee applies only to successful enrichment. |
| `onlyChangesSinceLastRun` | Return new IDs and asking-price/currency changes. Skip unchanged IDs without a listing fee. |
| `onlyNewSinceLastRun` | Return IDs not observed by this watch before. Default `false`. Strictly new listings, rather than changed existing cars. |
| `watchId` | Stable name for one monitoring history. Default `default`. Use letters, digits, underscores or hyphens. |
| `proxyConfiguration` | Default strategy tries datacenter first and retries refusals through German residential proxy. Explicit groups or custom URLs override the default. |

Filters apply together. Structured price, mileage, fuel, seller and postcode inputs override corresponding pasted search filters. A pasted search retains other source filters. Direct listing URLs select the supplied listing ID and ignore search filters. Category links are resolved to the site's make/model identifiers before filters are applied, because category pages themselves can discard query-string filters.

#### Berlin BMW price watch

```json
{
  "make": "BMW",
  "model": "320",
  "postcode": "10115",
  "radiusKm": 100,
  "priceMax": 30000,
  "yearFrom": 2018,
  "mileageMax": 120000,
  "maxItems": 20,
  "watchId": "berlin-bmw-prices"
}
```

#### Diesel dealer inventory with equipment

```json
{
  "make": "Audi",
  "model": "A4",
  "fuel": "diesel",
  "sellerType": "dealer",
  "maxItems": 20,
  "includeDetails": true,
  "watchId": "audi-diesel-stock"
}
```

#### An existing mobile.de search

```json
{
  "startUrls": [
    "https://suchen.mobile.de/fahrzeuge/search.html?vc=Car&ms=3500%3B10%3B%3B&fr=2018%3A&cn=DE"
  ],
  "maxItems": 50,
  "watchId": "saved-search"
}
```

### Monitor prices every day

Create a saved task with your normal query and a stable `watchId`, then add a daily schedule in Apify Console. Keep that watch's runs sequential. History survives new runs and container restarts. Watch names and users have separate histories.

On the first run, each delivered ID has `isNewSinceLastRun: true`, `previousPrice: null` and `priceChange: null`. On later full runs, the prior observed asking price becomes `previousPrice`. `priceChange` is current minus previous, so a negative number means a drop. For example, an observed change from EUR 25,000 to EUR 24,000 produces `priceChange: -1000`.

For economical daily alerts, set `onlyChangesSinceLastRun: true`: return new listings and changed asking prices, with unchanged inspections free. Keep both switches off for full snapshots. Turn it on for a feed of first-seen listing IDs. Both filtered modes cap inspected IDs at `maxItems`. Strict new-only takes precedence if both switches are on. A scan of twenty existing cars can therefore return no new listings. Increase the cap deliberately for a broader watch. Skipped existing IDs have no listing or detail fee; the normal start charge still applies.

#### Retained price history

Every watch keeps up to ten observed price states per listing, each with its currency and observation timestamp. An unchanged price increases `observationCount` without repeating a timeline point. First, lowest and highest observed prices remain available even after an older timeline point falls outside that ten-state window. Price extrema and drop counts restart if currency changes.

`priceDropCount` counts observed decreases; `totalPriceChange` compares the current price with the first priced observation. `daysObserved` measures time since this watch first saw the ID, not time advertised. `daysSincePriceChange` remains null until a change is observed. Reboots preserve observation counts.

The site's crossed-out asking price is separate: `advertisedPreviousPrice` and `advertisedPriceReduction` describe a displayed offer, without inventing its date. They do not become observations in your timeline. History and statistics are included in the basic fee and require no extra site requests.

### Understanding ratings, prices and conditions

`priceRating` retains mobile.de's displayed label, such as `Sehr guter Preis` or `Fairer Preis`. `priceRatingCode` contains the corresponding source code when available. `marketPriceRange` preserves the displayed rating scale's bounds and thresholds. Those bounds are a comparison scale shown by the source, not a confidence interval, an appraisal or a history of this particular car.

`powerHp` retains the source horsepower value; `powerHpUnit` identifies PS (metric horsepower) or hp. No conversion is guessed. `price` is the total asking price shown by the listing. Monthly finance or leasing instalments are not substituted for it. When full details expose VAT, `netPrice` and `vatRate` retain the published values. A missing VAT statement stays unknown. Seller declarations about accidents are preserved as statements from the listing, without vehicle-history verification.

Visible and all-time review counts are separate: `dealerReviewCount` and `dealerTotalReviewCount`. They are not interchangeable. Equipment and full specifications depend on the listing detail response. A failed enrichment keeps a useful search row, marks `detailsIncluded: false` and adds no detail fee.

### Pricing examples

Base prices below include one $0.002 start. More than one GB of memory increases start units.

| Job | Approximate total |
| --- | ---: |
| 1 basic listing | $0.0035 |
| 20 basic listings | $0.032 |
| 100 basic listings | $0.152 |
| 20 listings with full details | $0.062 |
| 100 listings with full details | $0.302 |
| Only-new scan with no new listings | $0.002 start charge; no listing fee |

Bronze receives 5% off listing and detail fees, Silver 10%, and Gold and higher tiers 20%. Proxy and platform usage are included. There is no separate fee for inspecting previously seen watch records. Failed source requests, duplicates within a run and note rows have no listing charge. The Actor checks whether the remaining spending limit can cover the whole row, including requested details, before saving a paid result. Set Apify's maximum charge for an additional spending ceiling.

### Use with the API

Use the Apify client or HTTP API. Keep your token in an environment variable and fetch the dataset after completion.

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('moggedhq/mobile-de-scraper').call({
    make: 'BMW', model: '320', maxItems: 20,
    postcode: '10115', watchId: 'daily-berlin'
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('moggedhq/mobile-de-scraper').call(run_input={
    'make': 'Volkswagen', 'model': 'Golf', 'maxItems': 20,
    'watchId': 'daily-golf'
})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

### Use with AI agents (MCP)

Connect Apify's MCP server at `https://mcp.apify.com` and request mobile.de listings with a make, model, postcode, price ceiling, result cap and stable watch name. Ask the agent to inspect both the dataset and `SUMMARY`. History begins with your own observations; the Actor does not call an AI provider.

### Coverage and limits

The scope is public car listings. Other vehicle types are not certified. Capped scans cannot establish sold or removed status. Unfinished watches must be resumed before reusing their name. The source limits deep pagination; the Actor stops after fifty pages per search and explains this in the summary. Narrow larger searches by model, year, region or price. Listings can change between requests; results are not a simultaneous market snapshot.

German postcode centres come from [GeoNames postal data](https://download.geonames.org/export/zip/), under Creative Commons Attribution 4.0. The 28 October 2026 directory contains 10,815 codes. Centres are approximate. Without a postcode, generated searches default to Germany.

Requests run serially with pacing. Refusals retry with fresh sessions and honour `Retry-After`. Residential proxy is used only after the default datacenter route fails, unless you explicitly choose it. Repeated source refusals produce free failure notes. Future source changes can require maintenance; no scraper can promise permanent access to a third-party site.

### Frequently asked questions

#### Does this require a mobile.de account?

No. Public signed-out data only; no login cookies or CAPTCHA solving.

#### Are private seller identities included?

Private contacts are omitted. Business contacts are included for dealers. Seller descriptions and personal reviews are excluded.

#### What happens if a request fails or the run restarts?

Unavailable inputs have no listing fee. Restarts recover delivered rows without duplicate fees. Resume unfinished watches before reusing their names.

For mobile.de scraper and mobile de car listings Germany workflows, start with the overview.

# Actor input Schema

## `startUrls` (type: `array`):

Optional HTTPS mobile.de URLs. Pasted search filters are preserved. Structured filters override matching URL filters. Direct listing URLs ignore search filters.

## `make` (type: `string`):

Manufacturer name, for example BMW, Volkswagen, Audi, Mercedes-Benz or Toyota. Resolved using mobile.de's own model catalogue. Used when startUrls is empty.

## `model` (type: `string`):

Model or model family, for example 320, Golf, A4 or C-Klasse. Requires make. Used when startUrls is empty.

## `priceMin` (type: `integer`):

Minimum asking price in EUR.

## `priceMax` (type: `integer`):

Maximum asking price in EUR.

## `yearFrom` (type: `integer`):

Earliest first-registration year, not manufacture year.

## `mileageMax` (type: `integer`):

Maximum odometer reading in kilometres.

## `fuel` (type: `string`):

Optional fuel filter applied by mobile.de.

## `postcode` (type: `string`):

Optional five-digit postcode. Radius uses an approximate GeoNames postcode centre. Unknown postcodes produce a free note, never a national fallback.

## `radiusKm` (type: `integer`):

Radius around the postcode centre. The site applies the distance filter; a postcode alone is not an exact postcode match.

## `sellerType` (type: `string`):

Any seller, registered business dealer, or private seller. Private seller names and phone numbers are never included.

## `maxItems` (type: `integer`):

Maximum distinct listings delivered, from 1 to 10,000. In only-new mode, also caps inspected IDs. $0.0015 per listing, another $0.0015 for full details, plus $0.002 start. 20 basic listings cost about $0.032.

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

Fetch full specifications, equipment, images and source-advertised reductions where shown. Adds $0.0015 only when details are delivered, for $0.003 total per detailed listing. Slower.

## `onlyNewSinceLastRun` (type: `boolean`):

Skip previously observed IDs. Strictly new listings only, including the first baseline run. To see price changes for existing cars, leave this off and reuse watchId. Inspections and skipped listings have no listing fee. Run a watch sequentially. maxItems caps the number inspected, so a run can return fewer new listings.

## `watchId` (type: `string`):

Reuse a stable ID on daily runs to compare prices and identify first-seen listings. Each user and Actor have separate named storage. Use a different ID for a separate history. Resume an unfinished run before reusing its watch ID. Keeps up to ten observed price states, observation counts and lifetime price extrema. This is your own observation history, without historical backfill.

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

Default: datacenter first, then German residential after a refusal. Fresh sessions and serial paced requests. Custom proxy URLs or explicit groups override this strategy.

## `onlyChangesSinceLastRun` (type: `boolean`):

Return first-seen listings and listings whose asking price or currency changed. Unchanged inspections have no listing or details fee. maxItems caps inspected IDs. Use a stable watchId and sequential runs. onlyNewSinceLastRun takes precedence if both options are on.

## Actor input object example

```json
{
  "make": "BMW",
  "model": "320",
  "fuel": "",
  "postcode": "10115",
  "radiusKm": 100,
  "sellerType": "any",
  "maxItems": 20,
  "includeDetails": false,
  "onlyNewSinceLastRun": false,
  "watchId": "default",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "onlyChangesSinceLastRun": false
}
```

# Actor output Schema

## `listings` (type: `string`):

No description

## `monitoring` (type: `string`):

No description

## `summary` (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 = {
    "make": "BMW",
    "model": "320",
    "postcode": "10115",
    "radiusKm": 100,
    "maxItems": 20,
    "includeDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("moggedhq/mobile-de-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 = {
    "make": "BMW",
    "model": "320",
    "postcode": "10115",
    "radiusKm": 100,
    "maxItems": 20,
    "includeDetails": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("moggedhq/mobile-de-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 '{
  "make": "BMW",
  "model": "320",
  "postcode": "10115",
  "radiusKm": 100,
  "maxItems": 20,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call moggedhq/mobile-de-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,moggedhq/mobile-de-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/2FLIm4DG00e7WVe62/builds/BHNzFel46be7TG9SV/openapi.json
