# mobile.de Scraper (`s-r/mobile-de-scraper`) Actor

A mobile.de scraper that returns vehicle listings with the asking price and its currency, the mileage and its unit, the first registration, power, fuel, the VAT disclosure, the seller and the site's own verdict on whether the price is fair. No login, no API key, no browser.

- **URL**: https://apify.com/s-r/mobile-de-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** E-commerce, Business
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 actor run starteds

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

## mobile.de Scraper

A mobile.de scraper that returns vehicle listings with the asking price and its currency, the mileage and its unit, the first registration, power, fuel, the VAT disclosure, the seller and the site's own verdict on whether the price is fair. No login, no API key, no browser.

Cars, motorbikes, motorhomes, vans, trucks and trailers, all from one input.

### What you get

- **The price with its currency, the mileage with its unit, and the seller with their country, on every single row.** mobile.de is a German marketplace but it carries Austrian, Dutch and Belgian stock, so `seller_country` is a real field rather than a constant. A price you cannot tell the denomination of is not comparable to anything
- **The site's own price verdict** as `price_rating_code`: `very_good_price`, `good_price`, `fair_price`, `increased_price`, `high_price` or `not_rated`. mobile.de has already compared each vehicle to its peers, and that comparison comes back with the row
- **The VAT disclosure.** `vat_note` and `vat_deductible` tell a business buyer whether the VAT on a vehicle can be reclaimed, which moves the real cost of a €30.000 car by nearly €5.000. It is on the listing and almost nothing else publishes it
- **Paid placements named, not hidden.** mobile.de mixes paid slots into the result list. They are excluded by default, and if you switch them on they arrive tagged in `placement` so they never quietly skew a price distribution
- **Price cuts.** Where a listing shows a struck-through earlier price, you get `price_was` and `price_reduction` next to the current asking price
- **New and used handled as the different shapes they are.** A used vehicle carries mileage and a registration date; a new one carries a consumption figure and a CO2 number instead. Both are parsed into their own fields rather than forced into one layout
- **Seller reputation** with the star rating and the number of ratings behind it, so a 5.0 from two customers is distinguishable from a 4.4 from 1.119
- **Exact pagination.** The site reports how many offers match, and the walk stops at the real end of that set
- **Charged per row you receive**, plus a flat start fee. No per-compute-unit billing

### Why scrape mobile.de

mobile.de is the largest vehicle marketplace in Germany, and Germany is the largest car market in Europe: **1.573.242 cars listed** as this was written, plus 76.785 motorbikes. For anyone valuing a vehicle, sourcing stock, or watching a competitor's forecourt, it is the market that sets the price.

There is no public API. The result pages are the product.

The part worth having is the combination of the site's own price verdict with the VAT disclosure. Together they answer the two questions a trade buyer actually asks: is this priced right against the market, and what does it really cost me after tax. Neither is derivable from a price alone.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `query` | string | no | – | Free text searched across the listing, for example `audi tts` |
| `url` | string | no | – | A mobile.de search URL to read instead of the fields below |
| `vehicle_category` | select | no | `car` | Cars, motorbikes, motorhomes, vans, trucks or trailers |
| `price_min` / `price_max` | integer | no | – | Asking-price bounds in euros |
| `year_min` | integer | no | – | Earliest first-registration year |
| `mileage_max` | integer | no | – | Upper odometer bound in kilometres |
| `power_min_kw` | integer | no | – | Lower engine-power bound in kilowatts |
| `fuel` | select | no | `any` | Petrol, diesel, electric, hybrid, LPG, CNG, hydrogen, ethanol |
| `condition` | select | no | `any` | New only, used only, or both |
| `damaged_only` | boolean | no | `false` | Only vehicles declared damaged or accident-involved |
| `sort` | select | no | `relevance` | Price up or down, mileage up, registration newest first |
| `include_paid_placements` | boolean | no | `false` | Return the paid slots too, tagged in `placement` |
| `limit` | integer | no | `40` | Vehicles to return, 1 to 1000 |
| `retries` | integer | no | `4` | Retry attempts per page |

### Output

```json
{
  "position": 1,
  "listing_id": "41700080368128",
  "url": "https://suchen.mobile.de/fahrzeuge/details.html?id=41700080368128",
  "placement": "organic",
  "title": "Audi TTS 2.0 TFSI Quattro|1of500|Competition|S-Tronic",
  "model": "Audi TTS",
  "version": "2.0 TFSI Quattro|1of500|Competition|S-Tronic",
  "price": 26490.0,
  "price_currency": "EUR",
  "price_text": "26.490 €",
  "price_was": null,
  "price_reduction": null,
  "price_rating": "Fairer Preis",
  "price_rating_code": "fair_price",
  "vat_note": null,
  "vat_deductible": false,
  "delivery_cost_extra": false,
  "first_registration": "06/2014",
  "first_registration_year": 2014,
  "mileage_km": 86373,
  "mileage_unit": "km",
  "power_kw": 200,
  "power_hp": 272,
  "fuel": "petrol",
  "fuel_raw": "Benzin",
  "consumption": null,
  "co2_g_per_km": null,
  "accident_free": true,
  "damaged": false,
  "is_new": false,
  "highlights": ["Inspektion+Zahnr. neu", "inkl. Garantie", "Finanzierung möglich"],
  "seller_name": "Autohaus Yaprak",
  "seller_country": "DE",
  "seller_zip": "63768",
  "seller_city": "Hösbach",
  "seller_rating": 4.8,
  "seller_rating_count": 204,
  "market": "de"
}
```

### Use cases

**Sourcing stock at the right price.** Filter on `price_rating_code` of `very_good_price` or `good_price`, add a mileage ceiling and a registration floor, and sort by price. mobile.de has already ranked each asking price against comparable vehicles, so you are reading a verdict rather than rebuilding one from a distribution you have to assemble first.

**Costing a purchase properly.** `vat_deductible` separates listings where VAT is stated and reclaimable from listings where it is not. On a €30.000 vehicle that is a €4.800 difference in real cost, and it is the single most common mistake in a cross-border buying spreadsheet.

**Watching a dealership.** Run a broad search, group rows by `seller_name` and `seller_zip`, and repeat on a schedule. What arrives, what leaves and what sits there getting price cuts is the whole picture of how a forecourt is trading, and `price_was` makes the cuts explicit.

**Building a residual-value dataset.** Mileage, registration, power, fuel and price on every row, across cars and motorbikes, is enough to fit a depreciation curve per model. Because `price_currency` and `mileage_unit` ride along on every row, that dataset joins cleanly against listings from any other market without a units audit first.

**Salvage and parts sourcing.** `damaged_only` returns the 47.000-odd vehicles declared damaged or accident-involved, with the same price and specification fields as clean stock.

### How it compares

| | this actor | `3x1t/mobile-de-scraper-ppr` | `blackfalcondata/mobile-de-scraper` |
|---|---|---|---|
| Per 1.000 listings | **$1,20** | $1,50 | $0,59 |
| Actor-start fee | **$0,001** | $0,002 | $0,005 |
| Input fields | **15** | 21 | 61 |
| Currency named on the row | **yes** | not stated | not stated |
| Mileage unit named on the row | **yes** | not stated | not stated |
| Seller country on the row | **yes** | not stated | not stated |
| Site's own price verdict | **yes** | not stated | not stated |
| VAT disclosure | **yes** | no | no |
| Paid placements labelled | **yes** | no | no |
| Motorbikes, vans, trucks | **yes** | cars | cars |

Honest about the other side: `blackfalcondata` is half our price per row and has 79 monthly users to our zero, and its 61-input schema exposes filters this actor asks you to express as a pasted search URL instead. `3x1t` has 140 monthly users. Neither publishes what its price figures are denominated in.

### Pricing

Two events. `listing` costs $0,0012 per vehicle returned, which is **$1,20 per 1.000**. A $0,001 start fee covers the run itself. All pricing is pay-per-event, so you pay for rows you actually receive, and a run the site turns away bills the start fee only. No per-compute-unit charges.

### Limits and gotchas

- **1.000 vehicles per run.** The run summary reports both how many offers matched and how many pages were read, so you always know whether you saw the whole set.
- **Make and model are not free-text fields on the site.** `query` searches the listing text, which is usually enough. For an exact make and model, apply the filters on mobile.de and paste the address into `url`.
- **A price of €1 is a real listing, not a parse error.** Some sellers list a placeholder price and invite an offer. Sort by price ascending and you will see them first.
- **New vehicles have no mileage and no registration date.** They carry `consumption` and `co2_g_per_km` instead, and `is_new` is true. Used vehicles are the other way round. Filtering on `mileage_km` therefore drops new stock, which is usually what you want.
- **`price_rating_code` is the site's opinion, not ours.** Some listings are simply unrated and come back as `not_rated` rather than being guessed at.
- **Sorting options are deliberately shorter than the site's.** mobile.de accepts two further sort tokens and returns the unsorted result set for both. They are not offered here rather than offered and quietly ignored.
- **Prices are euros.** `price_currency` says so on every row rather than leaving you to infer it from the domain.
- **A refused page and an empty search are different answers.** A search with no matches exits successfully saying so; a page the site turned away is reported with its own code and the summary flags it. You never get an empty dataset that silently means "blocked".

### FAQ

**Can I scrape mobile.de without an API key?**
Yes. There is no credential field in the input.

**What is `price_rating_code`?**
mobile.de's own rating of the asking price against comparable vehicles, normalised from its German wording into `very_good_price`, `good_price`, `fair_price`, `increased_price`, `high_price` or `not_rated`.

**Why does the currency appear on every row when the site is German?**
Because a number without its denomination cannot be joined to anything. The same reason `mileage_unit` and `seller_country` are on the row: mobile.de carries stock from several countries and a dataset that assumes otherwise breaks the first time it crosses a border.

**Can I get motorbikes and vans too?**
Yes, through `vehicle_category`. The result layout and the output fields are the same.

**How do I track one dealer's stock?**
Group rows by `seller_name` and `seller_zip`, then repeat the run on a schedule. `price_was` shows you what they have discounted since last time.

**What happens if a run returns nothing?**
The summary tells you which of the two reasons applies: no vehicles matched your filters, or the site turned the request away. They are never reported the same way.

### Related Actors

- [AutoScout24 Scraper](https://apify.com/s-r/autoscout24-scraper): the same job across nine European markets
- [VIN Decoder](https://apify.com/s-r/vin-decoder): decode a vehicle identification number
- [Marktplaats Scraper](https://apify.com/s-r/marktplaats-scraper): Dutch classifieds, including vehicles

# Actor input Schema

## `query` (type: `string`):

Free text searched across the listing, for example "audi tts" or "vw golf gti". Leave it empty to walk the whole market with the filters below.

## `url` (type: `string`):

A mobile.de search URL to read instead of building one from the fields below. Apply whatever filters you want on the site, then paste the resulting address here. Anything the site can filter on works this way, including make and model.

## `vehicle_category` (type: `string`):

Which part of the marketplace to search. Cars is the default; the other categories use the same result layout.

## `price_min` (type: `integer`):

Lower asking-price bound in euros. mobile.de quotes every listing in euros.

## `price_max` (type: `integer`):

Upper asking-price bound in euros.

## `year_min` (type: `integer`):

Only vehicles first registered in or after this year. New vehicles have no registration date and are excluded when this is set.

## `mileage_max` (type: `integer`):

Upper odometer bound in kilometres.

## `power_min_kw` (type: `integer`):

Lower engine-power bound in kilowatts. 147 kW is 200 metric horsepower.

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

Restrict to one fuel type. Each option was checked by reading the fuel field back off the returned vehicles.

## `condition` (type: `string`):

New vehicles, used vehicles, or both. New vehicles carry a fuel-consumption and CO2 figure but no mileage or registration date.

## `damaged_only` (type: `boolean`):

Return only vehicles the seller has declared damaged or accident-involved. Useful for salvage and parts buyers.

## `sort` (type: `string`):

Result order. Only orders whose effect is visible in the returned rows are offered here; mobile.de accepts other sort tokens and ignores them.

## `include_paid_placements` (type: `boolean`):

mobile.de mixes paid slots into the result list. They are excluded by default; switch this on to receive them, tagged in the placement field.

## `limit` (type: `integer`):

How many vehicles to return, 1 to 1000. A page carries 20 organic results and the walk stops at the real end of the result set rather than probing past it.

## `retries` (type: `integer`):

Retry attempts per page, each one a differently shaped client. Raise it if a run reports pages that were refused.

## Actor input object example

```json
{
  "query": "audi tts",
  "url": "https://suchen.mobile.de/fahrzeuge/search.html?isSearchRequest=true&s=Car&vc=Car&ms=1900%3B%3B%3B",
  "vehicle_category": "car",
  "fuel": "any",
  "condition": "any",
  "damaged_only": false,
  "sort": "relevance",
  "include_paid_placements": false,
  "limit": 40,
  "retries": 4
}
```

# Actor output Schema

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

One row per vehicle.

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

Total offers the site reports, pages fetched, how many rows carried a price, and whether the run was refused rather than empty.

## `errors` (type: `string`):

Per-page failures with a code and a redacted message.

# 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 = {
    "query": "audi tts",
    "vehicle_category": "car",
    "fuel": "any",
    "condition": "any",
    "sort": "relevance",
    "limit": 40,
    "retries": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/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 = {
    "query": "audi tts",
    "vehicle_category": "car",
    "fuel": "any",
    "condition": "any",
    "sort": "relevance",
    "limit": 40,
    "retries": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/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 '{
  "query": "audi tts",
  "vehicle_category": "car",
  "fuel": "any",
  "condition": "any",
  "sort": "relevance",
  "limit": 40,
  "retries": 4
}' |
apify call s-r/mobile-de-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/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/L3AygPGpqRUKfvwmr/builds/BYI94oVRo2Kqqs3TL/openapi.json
