# Standvirtual Car Listings Scraper (`marielise.dev/standvirtual-car-listings-scraper`) Actor

Search and export every car listing on Standvirtual, Portugal's used-car marketplace. Filter by make, model, year, price, mileage, fuel, drivetrain, body type, seats and dealer terms, or describe the car you want in plain language and let AI score every advert.

- **URL**: https://apify.com/marielise.dev/standvirtual-car-listings-scraper.md
- **Developed by:** [Marielise](https://apify.com/marielise.dev) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 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.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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

## Standvirtual Car Listings Scraper

**Standvirtual has no public API. This does.** Standvirtual is Portugal's used-car marketplace, and this Actor searches the whole of it: all 40,500 live listings, by make, model, price, mileage, drivetrain, district and a dozen other filters, back as clean structured data in seconds. Or skip the filters entirely: describe the car you want in plain English, "a rugged 4x4 with good reliability, under 25000", and it picks the specific vehicles worth checking, searches them, and scores every advert 0 to 100 with a reason. No browser, no proxies, so a 200-listing run takes about 15 seconds.

### Who this is for

**Private buyers.** You want to know what a car actually costs before you go and look at one, not guess from three tabs and a gut feeling. Search by what you know, or describe what you want and let it work out the makes and models for you.

**Independent dealers.** You need to see what is on the market in your district right now: what a comparable car is priced at, whether a competitor's stock moved, and where you sit against it.

**Importers and exporters.** You are sourcing vehicles for other markets, where a few hundred euros of spread per unit is the whole deal. Filter by origin, year and mileage across the whole of Standvirtual instead of opening two hundred tabs.

**Developers.** You are building a price index, a valuation model, a lead-generation pipeline or an automotive app, and you need a structured feed you can schedule, not a scraping project of your own.

### How to scrape Standvirtual

1. Click **Try for free** and sign in to Apify. A free account is enough to run this.
2. Set **Make** and **Model**, for example `BMW` and `Série 3`. Both are matched against Standvirtual's own vocabulary, so `VW`, `Volkswagen`, `Serie 3` and `Série 3` all resolve. Leave every field empty and the run falls back to a 20-listing demo search so you can see the output shape.
3. Narrow it with any of the filters: year, price, mileage, fuel, gearbox, drivetrain, body type, seats, doors, district, seller type and seller terms.
4. Set **Max listings to return**. This is the main cost lever, because the run bills per listing saved.
5. Click **Start** and watch the log. A 150-listing run finishes in under ten seconds.
6. Open the **Output** tab, pick a view, and export as JSON, CSV, Excel, XML, HTML or JSONL, or read the dataset from the API.

Instead of steps 2 and 3, type what you want into **Describe the car you want** and let Claude pick the filters and the vehicles worth searching for.

### How much it costs

Pay per event. You are billed for work that succeeded, never for work that failed.

| Event | Price | When it fires |
| --- | --- | --- |
| Actor start | $0.01 | Once per run, per GB of allocated memory. The 1 GB default bills as one unit. Always charged, including on a run that collects nothing. |
| Listing | $0.004 | Per car written to the dataset. Never more than `maxResults`. |
| Listing detail page | $0.002 | Per advert opened for colour, photos and consumption. Only when detail-page enrichment is on. A page that fails to load is not charged. |
| AI search plan | $0.15 | Once per run, only for a plain-language search on managed Claude access. Charged after the plan comes back, so a failed call costs nothing. |
| AI scoring, per 25 listings | $0.12 | Only on managed Claude access. Charged after each batch scores successfully. |

#### What a typical run costs

| Run | Cost |
| --- | --- |
| Demo run, empty input, 20 listings | $0.09 |
| 100 listings, filters only | $0.41 |
| 100 listings with detail-page enrichment | $0.61 |
| Plain-language search, 60 listings scored and ranked | $0.76 |
| The same, with detail-page enrichment | $0.88 |
| The most one run can bill | $13.12 |

That ceiling is 2,000 listings, all enriched, with a plan and the full 200-advert scoring allowance. Scoring stops at 200 adverts by design, so the AI cost cannot exceed $1.11 however large the run.

#### Avoiding the AI charges

Leave **Describe the car you want** empty and use the filters, or supply your own `anthropicApiKey` and pay Anthropic directly at cost. With your own key neither AI event is charged.

#### Capping what a run can spend

Set **Max total charge** in the run options and the Actor works within it rather than stopping: it skips the plan if the limit will not cover it, enriches only as many detail pages as the limit allows, and stops scoring at the last batch that fits. Each of those is reported in the log.

A run that is blocked, or that matches nothing, writes no dataset items and bills no per-listing charge. You will still see the run-start charge.

### What you can do with it

**Price-check before you buy.** Pull every comparable car in a district, sort by price, and see Standvirtual's own verdict, below, in line with, or above market, sitting next to each one.

**Watch a competitor's stock daily.** Schedule a run filtered to dealers in your region and diff listing IDs between runs to see exactly what arrived, what sold, and what was repriced overnight.

**Ask for a car in plain English.** "A rugged 4x4 with good reliability under 25000" becomes a shortlist of real vehicles, Land Cruiser, Hilux, Pajero, L200, Patrol and Defender, each searched and every advert scored: an 85/100 2019 L200 for "below-market price, low mileage and recent year", a 38/100 2003 Land Cruiser for "legendary reliable drivetrain but 390k km on an old auto box is a lot of risk for the price".

**Find genuine 4x4s, not badge-engineered crossovers.** A real drivetrain filter separates cars the site catalogues as four-wheel drive from small SUVs that only drive the front wheels.

**Source for export.** Filter by origin, year range and mileage cap across the whole country to shortlist cars worth moving, before you have picked up the phone.

**Feed a product.** Point a schedule and a webhook at it and let it top up your own database, price index or valuation model with fresh listings.

### Input

Every field is optional. Supply an empty input and the Actor runs a 20-listing demo search for a BMW Série 3, which is also what Apify's daily health check does.

The demo only triggers when the input is genuinely empty: no make, model, variant or description, no year, price or mileage bound, no district, no seats or doors, no seller terms, and every enum left on `any`. Setting any one of those runs the search you asked for, however wide.

#### Example input, filters

```json
{
  "make": "BMW",
  "model": "Série 3",
  "yearMin": 2018,
  "priceMax": 30000,
  "mileageMax": 150000,
  "fuelType": "diesel",
  "gearbox": "automatic",
  "sellerType": "dealer",
  "location": "Lisboa",
  "sortBy": "price-asc",
  "maxResults": 100,
  "enrichFromDetailPages": true
}
```

#### Example input, plain language

```json
{
  "describe": "a rugged 4x4 with good reliability, under 25000",
  "maxResults": 60,
  "aiRanking": true
}
```

That runs on managed Claude access and bills the plan once plus one scoring event per 25 adverts. Add `"anthropicApiKey": "sk-ant-..."` to pay Anthropic directly instead, and neither AI event is charged.

#### Input reference

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `describe` | string | | What you want, in plain language. Claude turns it into filters and a shortlist of vehicles. The API key is optional. |
| `anthropicApiKey` | string, secret | | Optional. Supply your own key to pay Anthropic directly and skip both AI events. Leave empty for managed access, billed per event. |
| `anthropicWorkspaceId` | string, secret | | Only for identity-linked Anthropic keys, which the API signals with `anthropic-workspace-id is required`. |
| `aiRanking` | boolean | `true` | Score every advert 0 to 100 against `describe` and sort best first. Ignored without `describe`. |
| `make` | string | | Manufacturer. Matched against Standvirtual's own make list; accents and casing do not matter. |
| `model` | string | | Model. Requires `make`. |
| `variant` | string | | Trim. Applied server-side when Standvirtual publishes a matching sub-model, otherwise matched against each advert's version and title. |
| `yearMin`, `yearMax` | integer | | Year of first registration. |
| `priceMin`, `priceMax` | integer | | Asking price in EUR. |
| `mileageMax` | integer | | Maximum kilometres. |
| `fuelType` | enum | `any` | `petrol`, `diesel`, `electric`, `hybrid` (all hybrid types), `lpg` (LPG and CNG). |
| `gearbox` | enum | `any` | `manual`, `automatic`. |
| `condition` | enum | `any` | `used`, `new`. |
| `sellerType` | enum | `any` | `private`, `dealer`. |
| `bodyType` | enum | `any` | `suv`, `compact`, `combi`, `city-car`, `sedan`, `coupe`, `cabrio`, `minivan`. A shape, not a drivetrain. |
| `drivetrain` | enum | `any` | `awd`, `front`, `rear`. `awd` is the real 4x4 filter. |
| `seats` | integer | | Exact seat count, 1 to 9. |
| `doors` | integer | | Exact door count, 2 to 6. Standvirtual counts the tailgate. |
| `financingAvailable` | boolean | `false` | Seller arranges credit. |
| `certifiedDealer` | boolean | `false` | ACAP-certified dealers only. |
| `acceptsTradeIn` | boolean | `false` | Seller takes your current car in part payment. |
| `vatDeductible` | boolean | `false` | VAT reclaimable, for company purchases. |
| `fuelPriceEurPerLitre` | number | see below | Overrides the built-in pump price. |
| `annualKm` | integer | `15000` | Expected annual distance for the yearly fuel estimate. |
| `location` | string | | Portuguese district. Unrecognised names are logged and the filter dropped. Açores and Madeira are not filterable. |
| `sortBy` | enum | `relevance` | `newest`, `price-asc`, `price-desc`, `mileage-asc`, `mileage-desc`. |
| `maxResults` | integer | `100` | 1 to 2000. Caps what is saved and therefore what is billed. |
| `enrichFromDetailPages` | boolean | `false` | Opens each advert for colour, the full photo set, the full description, consumption and CO2. One extra request per listing. |

### Output

One record per listing, written to the dataset in chunks of 50, so a run that is interrupted still leaves you the records it had already finished.

#### Field reference

**Populated when** is the column to read before you build on a field. `Always` means every successful run. `Enrichment` means you must set `enrichFromDetailPages`, and even then only sellers who published the figure have one. `AI ranking` means a `describe` search with ranking left on.

| Field | Type | Populated when | Description |
| --- | --- | --- | --- |
| `listingId` | string | Always | Standvirtual's advert ID. Stable across runs, so it is the key for diffing stock. |
| `url` | string | Always | Canonical advert URL. |
| `title` | string | Always | Advert title as published. |
| `make`, `model`, `variant` | string, null | Always | `model` is the specific designation (`320`), not the family (`Série 3`). |
| `year` | integer, null | Always | Year of first registration. |
| `mileageKm` | integer, null | Always | Odometer reading. |
| `fuelType` | string, null | Always | `petrol`, `diesel`, `electric`, `hybrid_petrol`, `hybrid_diesel`, `plugin_hybrid`, `lpg`, `cng`. |
| `gearbox` | string, null | Always | `manual` or `automatic`. |
| `drivetrain` | string, null | Enrichment | `awd`, `front` or `rear`. Recorded only on the advert's own page. |
| `enginePowerHp`, `engineCapacityCc` | integer, null | Usually | Power in Portuguese horsepower (cv), displacement in cm3. |
| `origin` | string, null | Usually | `Nacional`, `Importado` and similar. |
| `price` | string, null | Always | Price as displayed. |
| `priceEUR` | integer, null | Always | Price in whole euros, for sorting and aggregation. |
| `priceEvaluation` | string, null | Always | Standvirtual's own verdict: `below_market`, `in_line_with_market`, `above_market`. Null when the site declined to rate it, which is not the same as rating it average. |
| `location` | object | Always | `{ city, region }`. `region` is the district. |
| `sellerName` | string, null | Dealers only | Private sellers are not named by the site. |
| `sellerType` | string, null | Always | `private` or `dealer`. Dealers must warrant used cars in Portugal, and carry a `WARRANTY` badge. |
| `badges` | string\[] | Always | Empty array, never null. |
| `images` | string\[] | Always | Two sizes of the same thumbnail. The full photo set with enrichment. |
| `description` | string, null | Always | The card teaser. The full seller description with enrichment. |
| `parameters` | object\[] | Always | The advert's raw parameter array, including spec fields not normalised into a column. |
| `seats`, `doors` | integer, null | Enrichment | Seat and door count. |
| `co2GramsPerKm` | number, null | Enrichment | Published CO2. Roughly three quarters of adverts carry one. |
| `fuelConsumptionL100km` | number, null | Enrichment | Combined consumption, NEDC-weighted from the published urban and extra-urban pair, or derived from CO2. |
| `fuelPriceEurPerLitre` | number, null | With consumption | The pump price the estimate used, written onto the record so a stale estimate is visible as one. |
| `fuelCostPer100kmEur`, `annualFuelCostEur` | number, null | With consumption | Estimated fuel cost per 100 km and per year. |
| `maintenanceEurPerYear` | integer, null | AI ranking | Claude's estimate of annual servicing and wear. An informed opinion, not a quote. |
| `aiScore`, `aiReason` | integer, string, null | AI ranking | Match score 0 to 100 and one sentence explaining it. |
| `listedAt`, `scrapedAt` | string, null | Always | ISO 8601. |

Every field Standvirtual can omit comes back null rather than a substituted default. A missing mileage is unknown mileage, and a fabricated zero in a dataset people price cars against would be worse than an honest blank.

#### Output views

| View | What it shows | Needs |
| --- | --- | --- |
| `overview` | Title, price, price verdict, year, mileage, fuel, gearbox, district, seller type, URL. Only fields that populate on every run. | Nothing |
| `specs` | Every normalised specification field. | Enrichment for the spec columns |
| `media` | Title, images, description, price, URL. | Enrichment for full photo sets |
| `running_costs` | Consumption, CO2, fuel cost per 100 km and per year, maintenance estimate. | Enrichment, and AI ranking for maintenance |
| `ai_match` | Score, reasoning, maintenance estimate, and the fields you judge them against. | A description with ranking on |

`overview` is the safe default for anything automated: it is defined as the subset that is never null because of an option you did not switch on.

```
https://api.apify.com/v2/datasets/<datasetId>/items?view=overview&format=csv
https://api.apify.com/v2/datasets/<datasetId>/items?fields=listingId,priceEUR,url&format=jsonl
```

`location` is a nested object, so add `&flatten=location` when exporting to a tabular format without a view.

### Run it from your own code

Replace `<TOKEN>` with your Apify API token. The Actor is `marielise.dev~standvirtual-car-listings-scraper`.

#### Start a run

```bash
curl -X POST "https://api.apify.com/v2/acts/<ACTOR>/runs?token=<TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{ "make": "BMW", "model": "Série 3", "priceMax": 30000, "maxResults": 100 }'
```

The response carries `id`, `defaultDatasetId` and `defaultKeyValueStoreId`. Poll `GET /v2/actor-runs/<runId>` until `status` is `SUCCEEDED`.

#### Get the data back in one request

```bash
curl -X POST "https://api.apify.com/v2/acts/<ACTOR>/run-sync-get-dataset-items?token=<TOKEN>&view=overview&format=csv" \
  -H 'Content-Type: application/json' \
  -d '{ "make": "BMW", "model": "Série 3", "maxResults": 100 }'
```

The synchronous endpoint has a hard 300-second ceiling. A filters-only run of a few hundred listings finishes well inside it. A 2,000-listing run with enrichment will not, so start it asynchronously and use a webhook.

#### JavaScript client

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client
    .actor('marielise.dev/standvirtual-car-listings-scraper')
    .call({ make: 'BMW', model: 'Série 3', priceMax: 30000, maxResults: 100 });

const { items } = await client.dataset(run.defaultDatasetId).listItems({ view: 'overview' });
```

#### Python client

```python
from apify_client import ApifyClient

client = ApifyClient(token)

run = client.actor('marielise.dev/standvirtual-car-listings-scraper').call(run_input={
    'make': 'BMW',
    'model': 'Série 3',
    'maxResults': 100,
})

items = client.dataset(run['defaultDatasetId']).list_items(view='overview').items
```

#### Schedule it, and push results onward

Save your input as an Actor task, then attach a schedule to the task. Add a webhook on the **Integrations** tab for `ACTOR.RUN.SUCCEEDED` and read `resource.defaultDatasetId` from the payload rather than expecting the items inline. Apify retries a failed webhook and may deliver the same event twice, so key your receiver on `eventData.actorRunId`.

Worth registering `ACTOR.RUN.FAILED` and `ACTOR.RUN.TIMED_OUT` too: since this Actor does not throw on a site block, those events mean a genuine defect rather than a bad day at Standvirtual.

#### Monitoring stock incrementally

`listingId` is Standvirtual's own advert ID and is stable across runs, which makes a diff cheap: anything new is stock that arrived, anything missing has sold or been withdrawn, and a changed `priceEUR` is a repricing. Keep the filters in an Actor task so they cannot drift between runs and turn a filter change into a false "sold" signal.

### How a run ends

This Actor does not throw an error when Standvirtual blocks a request. A thrown error looks like a crash to Apify's health checks and to your own monitoring, so a blocked run finishes successfully instead: it writes zero dataset items, bills nothing beyond the run-start event, and leaves a status record explaining what happened.

In practice, **run status alone does not tell you whether a run collected data.** Check the record count, or read `OUTPUT.json` from the key-value store: a run that collected data does not write that file, so its presence is itself the signal that the run came back empty.

`OUTPUT.json` carries `status` (`blocked` or `no_results`), `blockReason`, a one-sentence `message`, an array of `nextSteps`, and a `meta` block with the search URL and every filter as it was actually requested.

| `blockReason` | Meaning | Worth retrying |
| --- | --- | --- |
| `challenge_page` | Standvirtual served a page with no data payload: an interstitial, a consent wall or a bot challenge. | Yes, it is decided per request |
| `http_status` | Every request came back with a non-success status. Transient rate limiting. | Yes, after a pause |
| `network` | Standvirtual could not be reached. A transport failure, not a refusal. | Yes |
| `parser_stale` | The site answered normally but the payload no longer holds what this Actor reads. A defect in the Actor. | No, report it |
| `unknown` | Nothing readable was collected and the cause could not be determined. | Check the diagnostics |

`no_results` is not a failure. It means Standvirtual answered normally and had nothing matching your filters.

`RUN_DIAGNOSTICS.json` is written on **every** run, successful or not: page counters, adverts seen before deduplication, the total Standvirtual reported, the filters it confirmed applying, what your search terms resolved to in its vocabulary, any terms it did not recognise, and the exact first search URL so you can open it in a browser and compare.

### How it works

Standvirtual is a Next.js site that embeds its full GraphQL search response in the HTML of every results page. This Actor reads that payload directly over plain HTTP. No browser, no proxies, no JavaScript execution, which is why a 200-listing run costs a fraction of a cent in compute and finishes in seconds.

It is deliberately polite, and there is nothing to configure. Search pages are requested one at a time with 400 ms between them. A failed request is retried three times with backoff, except a stale-parser failure, which is deterministic and is not retried. Detail pages are fetched four at a time. Pagination stops at 200 pages, so a defect can never walk the whole site.

#### The filters you ask for are the filters you get

Standvirtual quietly widens a search that would return too few results. It reports this in a `relaxation` field the page never shows: on one real query it stretched a €25,000 cap to €28,000 and a 2004 year floor back to 1999, then served the near-misses as if they matched. That is how a 1994 car appears under a 2004 filter.

This Actor reads that field, logs exactly which bound the site moved, and drops any advert outside the range you asked for. A narrow search returns few records or none, which is the honest answer, rather than a padded list that quietly breaks its own filters.

#### Finding real 4x4s

Set `drivetrain` to `awd` and you get the cars Standvirtual's catalogue records as Tracção Integral, which is where quattro, xDrive, 4Motion, 4MATIC, All4 and 4WD all live. On a diesel manual search from 2010 under €30k that narrows 7,012 results to 126. Combine it with `bodyType: "suv"` and you get 94 genuine off-roaders rather than front-wheel-drive crossovers.

Body type alone is not enough: on its own it returns Capturs, Jukes and C4 Cactuses, because most small SUVs sold in Portugal drive the front wheels only.

#### Searching in plain language

Naming vehicles narrows the search twice: once by your filters, once by make. A tight filter set can leave a named vehicle with almost nothing, even though the same filters across all makes still describe a real market. So when the shortlist does not fill `maxResults`, the Actor runs one more search with the same filters and no make, and merges it in. On one real query that turned 1 result into 51.

Planning runs on Claude Opus 5; scoring, where almost all the token spend goes, runs on Claude Sonnet 5.

#### What a car costs to run

Fuel is computed from the advert's own figures, never guessed. Where the seller published urban and extra-urban consumption, the two are combined in the NEDC weighting they are quoted under. Where they published only CO2, consumption is derived from it: grams of CO2 per litre burnt is fixed by chemistry, 2,640 for diesel and 2,392 for petrol, so 123 g/km is 4.7 l/100km and not an opinion.

The built-in prices are the Portuguese averages for the week of 17 to 23 August 2026: €1.954 for petrol, €2.026 for diesel. Pump prices move weekly, so set `fuelPriceEurPerLitre` for a current figure.

Maintenance is a different kind of number. Standvirtual publishes nothing about servicing costs, so there is nothing to compute from. `maintenanceEurPerYear` is filled only when AI ranking runs, and it is the model's judgement of what this vehicle costs to keep on the road at its age and mileage: an informed opinion, not data.

#### Buying from a stand, with credit and a warranty

Set `sellerType` to `dealer` and you drop private sales entirely. That one setting also gets you the warranty: Portuguese law obliges dealers to warrant the used cars they sell, and the `WARRANTY` badge appears on 32 of 32 dealer adverts and 0 of 32 private ones, so there is no separate warranty filter to set.

Four further terms stack on top: `financingAvailable` (about 24,000 listings), `acceptsTradeIn` (about 19,800), `certifiedDealer` (about 1,965, certified by ACAP, the Portuguese motor trade association) and `vatDeductible` (about 8,475).

### Limits and known behaviour

- **Colour, photos, descriptions, consumption and drivetrain need enrichment.** Standvirtual's search cards do not carry them. Turn on `enrichFromDetailPages` at one extra request per listing.
- **Not every seller publishes consumption.** Of 16 diesel adverts sampled, 11 carried consumption and 12 carried CO2, so expect roughly a third of a result set to come back without a fuel estimate.
- **The drivetrain filter only sees catalogue-matched adverts**, roughly two thirds of the market. A car with "4x4" typed in its title but no catalogue match is invisible to it.
- **`location` is a district, not a city.** A city name only works when it doubles as its district capital.
- **Unrecognised makes and models are logged and dropped**, not rejected. The run still returns results, just wider ones, so it is worth checking the log on a scheduled run.
- **There is no free-text site search.** Standvirtual's own `q` parameter is ignored server-side and always returns full inventory, so this Actor does not expose it as a filter.

### FAQ

**How many listings can I get?**
Up to 2,000 per run. Standvirtual serves 32 adverts per page, so whole pages are fetched, but it never saves or bills more than `maxResults`.

**Do I need an Anthropic API key for the plain-language search?**
No. Leave it empty and the search runs on managed Claude access, billed per event. Supply your own key to pay Anthropic directly instead.

**Why did my run succeed but return nothing?**
A site block is reported as a successful run with a status record, not a crash, so check `OUTPUT.json` for the reason and the next steps.

**Can I cap what a run spends?**
Yes. Set **Max total charge** in the run options. The Actor degrades rather than failing, and reports each reduction in the log.

**Is it legal to scrape Standvirtual?**
This Actor reads only publicly available advert data that Standvirtual itself embeds in its own public search pages. Private sellers are not named by the site, so `sellerName` is null for them. Check Standvirtual's terms and your own jurisdiction before commercial use, and take legal advice if the answer matters to your business.

### Support

Found a bug, want a field added, or need a filter that is not exposed yet? Open an issue on the Actor's **Issues** tab and include the run ID: `RUN_DIAGNOSTICS.json` from that run shows at a glance whether it was a block, a site change or a defect.

# Actor input Schema

## `describe` (type: `string`):

Say what you are after in plain language, e.g. 'a rugged 4x4 with good reliability under 25000' or 'small cheap automatic for city driving'. Claude turns it into Standvirtual filters, picks the specific vehicles worth searching for, and scores every advert against what you asked. Runs on managed Claude access by default and is billed as two pay-per-event charges; supply your own Anthropic API key below to pay Anthropic directly instead. Leave empty to search with the fields below.

## `anthropicApiKey` (type: `string`):

Optional. Leave empty and the plain-language search runs on our managed Claude access, billed as two pay-per-event charges: one for planning the search and one per 25 adverts scored. Supply your own key from console.anthropic.com to pay Anthropic directly instead, and neither AI event is charged. Your key is used only for this run's planning and scoring.

## `anthropicWorkspaceId` (type: `string`):

Only needed if your Anthropic key is identity-linked, which the API signals with 'anthropic-workspace-id is required'. Copy the workspace ID from console.anthropic.com. Leave empty for a standard API key.

## `aiRanking` (type: `boolean`):

Scores each advert 0-100 against your description and sorts the best match first, adding 'aiScore' and 'aiReason' to every record. This is where reliability, running costs and trim fit get judged, none of which Standvirtual can filter on. Costs one Claude request per 25 adverts, and the first 200 adverts are scored.

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

Manufacturer to search for, e.g. 'Porsche', 'Volkswagen', 'BMW'. Matched against Standvirtual's own make list, so accents and casing do not matter. Leave every vehicle field empty to run a demo search.

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

Model to search for, e.g. '911', 'Golf', 'Série 3'. Requires Make. 'Serie 3', 'Série 3' and 'serie-3' all resolve to the same model.

## `variant` (type: `string`):

Optional trim, e.g. 'Carrera S', '320d', 'GTI'. When Standvirtual publishes a matching sub-model the filter is applied server-side; otherwise the variant is matched against each advert's own version and title.

## `yearMin` (type: `integer`):

Earliest year of first registration.

## `yearMax` (type: `integer`):

Latest year of first registration.

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

Minimum asking price in euros.

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

Maximum asking price in euros.

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

Maximum odometer reading in kilometres.

## `fuelType` (type: `string`):

Fuel type. 'Hybrid' covers petrol hybrids, diesel hybrids and plug-in hybrids. 'LPG / CNG' covers both gas fuels.

## `gearbox` (type: `string`):

Transmission type.

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

New or used vehicles.

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

Private sellers or professional dealers.

## `bodyType` (type: `string`):

Body shape. Standvirtual publishes no drivetrain filter at all, so 'SUV / off-road' is the closest available stand-in for a 4x4 search: it is where the site files off-roaders and pickups. It selects a shape, not a drivetrain, so two-wheel-drive SUVs match it too.

## `drivetrain` (type: `string`):

Driven wheels. 'All-wheel drive' is the real 4x4 filter: it returns cars the manufacturer catalogue records as Tracção Integral, so quattro, xDrive, 4Motion, 4MATIC and All4 all match. Standvirtual only fills this field from its catalogue, so roughly a third of adverts carry no drivetrain and are excluded whenever this filter is set.

## `seats` (type: `integer`):

Exact seat count, as Standvirtual files it (Lotação). Common values are 2, 4, 5, 7 and 9; a 7 finds the people carriers and three-row SUVs.

## `doors` (type: `integer`):

Exact door count. Standvirtual counts the tailgate, so a five-door hatchback is 5 and a coupe is 2 or 3.

## `fuelPriceEurPerLitre` (type: `number`):

Used to estimate running costs. Leave empty to use the Portuguese average for the week of 17-23 August 2026: 1.954 for petrol, 2.026 for diesel. Pump prices move weekly, so set your own for an up-to-date figure.

## `annualKm` (type: `integer`):

Your expected annual distance, used for the yearly fuel estimate. Defaults to 15000, roughly the Portuguese average.

## `financingAvailable` (type: `boolean`):

Only adverts where the seller offers credit (Possibilidade de financiamento). About 24,000 of roughly 40,000 listings qualify.

## `certifiedDealer` (type: `boolean`):

Only dealers certified by ACAP, Portugal's automotive trade association. A much smaller and better-vetted pool: around 1,965 listings.

## `acceptsTradeIn` (type: `boolean`):

Only sellers who take your current car as part payment (Aceita retoma). Around 19,800 listings.

## `vatDeductible` (type: `boolean`):

Only adverts where the VAT can be reclaimed (IVA dedutivel), which matters when buying through a company. Around 8,475 listings.

## `location` (type: `string`):

Portuguese district to search in, e.g. 'Lisboa', 'Porto', 'Braga'. Standvirtual filters by district rather than by city, so a city name only works when it matches its district capital. Unrecognised names are reported in the log and the filter is dropped. The island regions (Açores, Madeira) are not filterable.

## `sortBy` (type: `string`):

Result order. Combine 'Lowest price first' with a max results cap to sample the cheap end of a market.

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

Maximum number of listings to collect. This is the main cost lever: the run charges per listing returned. A run that finds nothing writes no records, so no per-listing charges apply, though the run-start charge always does. Standvirtual serves 32 adverts per page, so this rounds up to whole pages.

## `enrichFromDetailPages` (type: `boolean`):

Opens each advert's own page to read the exterior colour, the complete photo set and the full seller description, none of which Standvirtual puts on a search card. Costs one extra request per listing, so a large run takes noticeably longer. Leave off for a fast run where 'colour' is null, 'description' is the short teaser and 'images' holds the thumbnail only.

## Actor input object example

```json
{
  "aiRanking": true,
  "make": "BMW",
  "model": "Série 3",
  "fuelType": "any",
  "gearbox": "any",
  "condition": "any",
  "sellerType": "any",
  "bodyType": "any",
  "drivetrain": "any",
  "annualKm": 15000,
  "financingAvailable": false,
  "certifiedDealer": false,
  "acceptsTradeIn": false,
  "vatDeductible": false,
  "sortBy": "relevance",
  "maxResults": 100,
  "enrichFromDetailPages": false
}
```

# Actor output Schema

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

Every scraped advert with the fields cars are usually compared on: price, price verdict, year, mileage, fuel, gearbox, district and seller type

## `specs` (type: `string`):

The same listings with every normalised specification field, for spec-level comparison and import sourcing

## `media` (type: `string`):

Listing photos and full seller descriptions. Requires detail-page enrichment.

## `runningCosts` (type: `string`):

Consumption, CO2 and estimated fuel and maintenance cost per listing. Requires detail-page enrichment.

## `aiMatch` (type: `string`):

Scores and reasoning from a plain-language search. Requires a description with ranking on.

## `keyValueStore` (type: `string`):

Run diagnostics, and the status record written when a run collects nothing

# 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": "Série 3"
};

// Run the Actor and wait for it to finish
const run = await client.actor("marielise.dev/standvirtual-car-listings-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": "Série 3",
}

# Run the Actor and wait for it to finish
run = client.actor("marielise.dev/standvirtual-car-listings-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": "Série 3"
}' |
apify call marielise.dev/standvirtual-car-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,marielise.dev/standvirtual-car-listings-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/0PELVi39IKaWJna5y/builds/fprYyZwjtQsrI4XCO/openapi.json
