Standvirtual Car Listings Scraper avatar

Standvirtual Car Listings Scraper

Pricing

from $4.00 / 1,000 listings

Go to Apify Store
Standvirtual Car Listings Scraper

Standvirtual Car Listings Scraper

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.

Pricing

from $4.00 / 1,000 listings

Rating

0.0

(0)

Developer

Marielise

Marielise

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

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.

EventPriceWhen it fires
Actor start$0.01Once 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.004Per car written to the dataset. Never more than maxResults.
Listing detail page$0.002Per 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.15Once 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.12Only on managed Claude access. Charged after each batch scores successfully.

What a typical run costs

RunCost
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

{
"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

{
"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

FieldTypeDefaultNotes
describestringWhat you want, in plain language. Claude turns it into filters and a shortlist of vehicles. The API key is optional.
anthropicApiKeystring, secretOptional. Supply your own key to pay Anthropic directly and skip both AI events. Leave empty for managed access, billed per event.
anthropicWorkspaceIdstring, secretOnly for identity-linked Anthropic keys, which the API signals with anthropic-workspace-id is required.
aiRankingbooleantrueScore every advert 0 to 100 against describe and sort best first. Ignored without describe.
makestringManufacturer. Matched against Standvirtual's own make list; accents and casing do not matter.
modelstringModel. Requires make.
variantstringTrim. Applied server-side when Standvirtual publishes a matching sub-model, otherwise matched against each advert's version and title.
yearMin, yearMaxintegerYear of first registration.
priceMin, priceMaxintegerAsking price in EUR.
mileageMaxintegerMaximum kilometres.
fuelTypeenumanypetrol, diesel, electric, hybrid (all hybrid types), lpg (LPG and CNG).
gearboxenumanymanual, automatic.
conditionenumanyused, new.
sellerTypeenumanyprivate, dealer.
bodyTypeenumanysuv, compact, combi, city-car, sedan, coupe, cabrio, minivan. A shape, not a drivetrain.
drivetrainenumanyawd, front, rear. awd is the real 4x4 filter.
seatsintegerExact seat count, 1 to 9.
doorsintegerExact door count, 2 to 6. Standvirtual counts the tailgate.
financingAvailablebooleanfalseSeller arranges credit.
certifiedDealerbooleanfalseACAP-certified dealers only.
acceptsTradeInbooleanfalseSeller takes your current car in part payment.
vatDeductiblebooleanfalseVAT reclaimable, for company purchases.
fuelPriceEurPerLitrenumbersee belowOverrides the built-in pump price.
annualKminteger15000Expected annual distance for the yearly fuel estimate.
locationstringPortuguese district. Unrecognised names are logged and the filter dropped. Açores and Madeira are not filterable.
sortByenumrelevancenewest, price-asc, price-desc, mileage-asc, mileage-desc.
maxResultsinteger1001 to 2000. Caps what is saved and therefore what is billed.
enrichFromDetailPagesbooleanfalseOpens 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.

FieldTypePopulated whenDescription
listingIdstringAlwaysStandvirtual's advert ID. Stable across runs, so it is the key for diffing stock.
urlstringAlwaysCanonical advert URL.
titlestringAlwaysAdvert title as published.
make, model, variantstring, nullAlwaysmodel is the specific designation (320), not the family (Série 3).
yearinteger, nullAlwaysYear of first registration.
mileageKminteger, nullAlwaysOdometer reading.
fuelTypestring, nullAlwayspetrol, diesel, electric, hybrid_petrol, hybrid_diesel, plugin_hybrid, lpg, cng.
gearboxstring, nullAlwaysmanual or automatic.
drivetrainstring, nullEnrichmentawd, front or rear. Recorded only on the advert's own page.
enginePowerHp, engineCapacityCcinteger, nullUsuallyPower in Portuguese horsepower (cv), displacement in cm3.
originstring, nullUsuallyNacional, Importado and similar.
pricestring, nullAlwaysPrice as displayed.
priceEURinteger, nullAlwaysPrice in whole euros, for sorting and aggregation.
priceEvaluationstring, nullAlwaysStandvirtual'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.
locationobjectAlways{ city, region }. region is the district.
sellerNamestring, nullDealers onlyPrivate sellers are not named by the site.
sellerTypestring, nullAlwaysprivate or dealer. Dealers must warrant used cars in Portugal, and carry a WARRANTY badge.
badgesstring[]AlwaysEmpty array, never null.
imagesstring[]AlwaysTwo sizes of the same thumbnail. The full photo set with enrichment.
descriptionstring, nullAlwaysThe card teaser. The full seller description with enrichment.
parametersobject[]AlwaysThe advert's raw parameter array, including spec fields not normalised into a column.
seats, doorsinteger, nullEnrichmentSeat and door count.
co2GramsPerKmnumber, nullEnrichmentPublished CO2. Roughly three quarters of adverts carry one.
fuelConsumptionL100kmnumber, nullEnrichmentCombined consumption, NEDC-weighted from the published urban and extra-urban pair, or derived from CO2.
fuelPriceEurPerLitrenumber, nullWith consumptionThe pump price the estimate used, written onto the record so a stale estimate is visible as one.
fuelCostPer100kmEur, annualFuelCostEurnumber, nullWith consumptionEstimated fuel cost per 100 km and per year.
maintenanceEurPerYearinteger, nullAI rankingClaude's estimate of annual servicing and wear. An informed opinion, not a quote.
aiScore, aiReasoninteger, string, nullAI rankingMatch score 0 to 100 and one sentence explaining it.
listedAt, scrapedAtstring, nullAlwaysISO 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

ViewWhat it showsNeeds
overviewTitle, price, price verdict, year, mileage, fuel, gearbox, district, seller type, URL. Only fields that populate on every run.Nothing
specsEvery normalised specification field.Enrichment for the spec columns
mediaTitle, images, description, price, URL.Enrichment for full photo sets
running_costsConsumption, CO2, fuel cost per 100 km and per year, maintenance estimate.Enrichment, and AI ranking for maintenance
ai_matchScore, 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

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

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

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

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.

blockReasonMeaningWorth retrying
challenge_pageStandvirtual served a page with no data payload: an interstitial, a consent wall or a bot challenge.Yes, it is decided per request
http_statusEvery request came back with a non-success status. Transient rate limiting.Yes, after a pause
networkStandvirtual could not be reached. A transport failure, not a refusal.Yes
parser_staleThe site answered normally but the payload no longer holds what this Actor reads. A defect in the Actor.No, report it
unknownNothing 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.