blocket.se

search-cars-by-filters

Installation

Adds this website's skill for your agents

 

Summary

Search Blocket.se for cars matching user-supplied filters — make, model, price range, year, mileage, fuel, transmission, body type, region, equipment, dealer-vs-private — and return matching listings with full per-car metadata. Read-only; uses Blocket's public JSON API.

FIG. 01
FIG. 02
FIG. 03
FIG. 04
SKILL.md
255 lines

Blocket Car Search by Filters

Purpose

Search Sweden's largest classifieds site, Blocket, for used / new / leasing cars matching user-supplied filters — make, model, price range, model year, mileage, fuel type, transmission, body type, region, equipment, dealer-vs-private, colour, free-text query — and return the matching listings with full per-car metadata (make / model / model spec, year, mileage, price in SEK, fuel, transmission, dealer segment, registration number, VIN, location with lat/lon, dealer name, canonical URL, image URL, posting timestamp). Read-only — never opens listings, contacts sellers, or submits saved-search alerts.

When to Use

  • "Find me used Volvos in the 150,000–300,000 kr range from 2018 or later under 8,000 mil."
  • Daily / hourly monitoring of new listings matching saved search criteria.
  • Cross-region market analysis (price-per-year curves, supply by län/region).
  • Anywhere a user wants to filter Blocket's /mobility/search/car UI by structured criteria, including drilling down to specific series (e.g. Volvo V60-Serie) or specific models (e.g. Audi A4 Avant).

Workflow

Blocket's React app is a thin client over a public JSON API at https://www.blocket.se/mobility/search/api/search/SEARCH_ID_CAR_USED — no auth, no cookies, no anti-bot, no residential proxy required. Direct browse cloud fetch returns 200 with the full result set including a complete enum-of-filters block. Lead with the API path; the browser path is a slow, identical-data fallback (you'd be parsing the same JSON the page hydrates from). Both --verified and --proxies flags are unnecessary for the API.

1. Map user-supplied filter inputs to Blocket's parameter schema

User inputURL parameterValue formatNotes
make (single or multi)variant0.{brandId}Repeat &variant= for multi-make. See brand→id table in Site-Specific Gotchas.
series within makevariant1.{brandId}.{seriesId}E.g. Volvo V60-Serie = 1.818.2843
specific modelvariant2.{brandId}.{seriesId}.{modelId}E.g. Abarth 595 = 2.8093.2476.2000413
min/max price (SEK)price_from / price_tointegerSlider step 10000, max 700000 (700k+)
min/max model yearyear_from / year_tointegerRange 1950–2027, includes future model years
min/max mileagemileage_from / mileage_tointeger in mil (Swedish miles)1 mil = 10 km — pre-convert km÷10
fuel typefuelinteger enum1=Bensin, 2=Diesel, 4=El, 2441=Etanol (FFV), 3=Fordonsgas (CNG); more for hybrids
transmissiontransmission1=Manuell, 2=Automatisk, 583=Sekventiell
body typebody_type1=Halvkombi 3-d, 2=Halvkombi 5-d, 5=Familjebuss, 6=Coupé, 7=Cabriolet, …Full list in filters[].filter_items of any response
dealer / privatedealer_segment2=Företag (dealer), 3=Privat
sales formsales_form1=Used, 2=New, 5=LeasingDefault returns all three
wheel drivewheel_drive1=RWD (Bakhjuls), 2=AWD (Fyrhjuls), 3=FWD (Framhjuls), 1729=Tvåhjulsdriven
exterior colourexterior_colourinteger enum (1=Beige, 2=Blå, 4=Brun, 6=Grå, …)
location (län/region)location0.{regionId} (län) or 1.{regionId}.{kommunId}E.g. Stockholm län = 0.300001, Göteborg-Kungsbacka-radius via polylocation+radius
free-text queryqURL-encoded stringMatches across heading + model_specification
sort ordersortPUBLISHED_DESC (default), PRICE_ASC, PRICE_DESC, YEAR_ASC, YEAR_DESCPUBLISHED_ASC is silently rejected and falls back to PUBLISHED_DESC
pagepageinteger ≥ 1
page sizerowsinteger (default 49, max practical ≈ 100)

2. Hit the API

URL="https://www.blocket.se/mobility/search/api/search/SEARCH_ID_CAR_USED?variant=0.818&price_from=150000&price_to=300000&year_from=2018&mileage_to=8000&fuel=2&sort=PRICE_ASC&rows=20"
browse cloud fetch "$URL"

Note: despite the name SEARCH_ID_CAR_USED, this is the only valid SEARCH_ID_CAR_* key — SEARCH_ID_CAR, SEARCH_ID_CAR_NEW, SEARCH_ID_CAR_LEASING, SEARCH_ID_CAR_ALL all return HTTP 400. New cars and leasing are surfaced inside the same endpoint via the sales_form filter (1=used, 2=new, 5=leasing). No header tweaking required — a bare GET works.

3. Decode the response

{
  "docs": [ /* up to `rows` listings */ ],
  "filters": [ /* full enum of all available filters and their hit counts at the current scope */ ],
  "metadata": {
    "result_size": { "match_count": 17571, "group_count": 17571 },
    "paging":      { "param": "page", "current": 1, "last": 50 },
    "selected_filters": [{ "filter_name": "variant", "display_name": "Volvo", … }],
    "sort": "PRICE_ASC",
    "title": "Volvo",
    "uuid": "1f8fa589-…",
    "is_end_of_paging": false
  }
}
  • Total match count is metadata.result_size.match_count (NOT docs.length, which is just the current page).
  • Pagination: keep requesting &page=N until current === last or is_end_of_paging === true. Last page is metadata.paging.last.
  • Filter validation: metadata.selected_filters[].display_name echoes back the friendly name Blocket resolved (e.g. "Volvo" for variant=0.818). If display_name equals the raw value (e.g. "0.7088"), Blocket didn't recognize the code — the filter was silently dropped, returning a larger result set than expected. Always cross-check selected_filters against the inputs you sent.

4. Per-listing fields (each docs[i])

{
  "type": "motor",
  "ad_id": 22734541,
  "id":   "22734541",                                  // same as ad_id, as string
  "main_search_key": "SEARCH_ID_CAR_USED",
  "heading":      "Volvo V60",
  "facade_title": "Volvo V60",                         // duplicate of heading
  "make":  "Volvo",
  "series": "V60-Serie",
  "model": "V60",
  "model_specification": "Recharge T6 AWD Geartronic", // trim level / engine
  "year": 2020,
  "mileage": 15680,
  "mileage_unit": "SCANDINAVIAN_MILE",                 // = mil = 10 km
  "price": { "amount": 248000, "currency_code": "SEK", "price_unit": "kr" },
  "fuel": "Plug-in Bensin",                            // human-readable, not enum id
  "transmission": "Automatisk",
  "dealer_segment": "Privat",                          // or "Företag"
  "organisation_name": "Bilbolaget Uppsala Kungsgatan",// only when dealer_segment === "Företag"
  "org_id": "9351069",                                 // dealer org id (or seller id for private)
  "regno":  "GRJ17P",                                  // Swedish registration number
  "chassis_number": "YV1ZWBFUDL1381720",               // VIN
  "registration_class": { "id": 1, "value": "Personbil" },
  "location": "Stora Höga",                            // city / locality (Swedish)
  "coordinates": { "lat": 58.02042, "lon": 11.81809, "accuracy": 5 },
  "canonical_url": "https://www.blocket.se/mobility/item/22734541",
  "image": { "url": "https://images.blocketcdn.se/dynamic/default/item/.../...", "width": 4032, "height": 3024, "aspect_ratio": 1.333 },
  "timestamp": 1779471110000,                          // ms since epoch — posting time
  "service_documents": ["Service"],                    // array; ["Service"] means full service history advertised
  "used_car_of_the_year": [],
  "sales_form": 1,                                     // 1=used, 2=new, 5=leasing
  "driving_range": 50,                                 // EV/PHEV-only, in km
  "extras": [],
  "labels": [],
  "flags": [],
  "ad_type": 20
}

When emitting clean output, normalize mileage to km (mileage * 10) and posted-at to ISO 8601 (new Date(timestamp).toISOString()).

Browser fallback (when the API is unavailable, e.g. CDN incident)

The same query params work on the human-facing URL — Blocket's React app reads them at hydration time, then re-issues the same /mobility/search/api/search/SEARCH_ID_CAR_USED?… GET that returns the JSON the agent could have fetched directly. Use only if the API itself is failing:

  1. browse cloud sessions create --keep-alive (no --verified or --proxies needed — Blocket has no anti-bot wall).
  2. browse open "https://www.blocket.se/mobility/search/car?variant=0.818&price_from=150000&price_to=300000&year_from=2018".
  3. First visit only: accept the Vend cookie banner inside the Cookieinställningar iframe — browse click @<ref-for-Godkänn alla>. Subsequent loads reuse the session's euconsent-v2 cookie.
  4. browse wait load, then browse wait timeout 3000 (lists hydrate ~2s after load).
  5. browse get title → result count is in the title text ("… | 22 426 bilar | Blocket"); or browse snapshot → article refs are [N-M] article per listing.
  6. Same per-listing fields are readable from each article — heading, price (kr), location, mileage, year — but at ~30× the wall-clock cost of the direct API hit (~15s vs ~0.5s).

Site-Specific Gotchas

  • Mileage is in mil (Swedish miles), not kilometersmileage_unit: "SCANDINAVIAN_MILE" in responses, and mileage_from / mileage_to URL params take mil values where 1 mil = 10 km. A listing with mileage: 15680 means 156,800 km. Slider max is 20,000 mil (200,000 km). Pre-convert user-supplied kilometer thresholds by dividing by 10 before passing them to the API. Forgetting this produces a result set off by 10×.

  • Brand filter is encoded as a hierarchical variant code, NOT make / brand / manufacturer — none of the obvious URL param names work (verified: make=Volvo, brand=Volvo, manufacturer=Volvo, fabrikat=Volvo, marke=Volvo, make_model=Volvo, make[]=Volvo are all silently dropped and return the unfiltered 144k-car set). The actual param is variant=0.{brandId} with these confirmed IDs (extracted from /api/search filters[name=variant].filter_items[], 2026-05-22):

    BrandvariantBrandvariantBrandvariant
    Audi0.744BMW0.749Mercedes-Benz0.785
    Volvo0.818Volkswagen0.817Skoda0.808
    Toyota0.813Tesla0.8078Ford0.767
    Kia0.777Hyundai0.772Nissan0.792
    Mazda0.784Honda0.771Lexus0.782
    Renault0.804Peugeot0.796Citroen0.757
    Opel0.795Subaru0.810Porsche0.801
    Saab0.806Mitsubishi0.787Jaguar0.775
    Land Rover0.781Chevrolet0.753Alfa Romeo0.3233

    Full 150-brand table lives in any response's filters[name=variant].filter_items[]. Always fetch the variant tree once to discover unmapped brands (cheaper / Cupra / BYD / Aston Martin / etc. use the same encoding but unfamiliar IDs). Series and specific-model selectors use longer codes: 1.{brand}.{series} (e.g. Volvo V60-Serie = 1.818.2843) and 2.{brand}.{series}.{model} (e.g. Abarth 595 = 2.8093.2476.2000413). Multi-brand search works by repeating &variant= — they OR together.

  • SEARCH_ID_CAR_USED is the only valid path segmentSEARCH_ID_CAR, SEARCH_ID_CAR_NEW, SEARCH_ID_CAR_LEASING, SEARCH_ID_CAR_ALL all return HTTP 400. The path name is misleading: it's actually the universal car-search endpoint, and new/leasing listings are surfaced by setting sales_form=2 or sales_form=5 respectively. By default (no sales_form) all three are returned.

  • Validate metadata.selected_filters before trusting the result count. Blocket silently drops unrecognized values — pass variant=0.7088 (a non-existent brand id) and the API returns a 200 with an effectively-unfiltered ~144k results, but the selected_filters block echoes back display_name: "0.7088" instead of a brand name. If display_name equals the raw code, the filter didn't take.

  • The on-page URL doesn't always update when filters are toggled via clicks. Clicking the Volvo checkbox in the sidebar swaps the page title to "Volvo till salu | Blocket" but the address bar still shows the bare URL plus an A/B-test variant=0.818 param (which happens to also be Volvo's brand id — confusing but coincidental in the bare case). The filter IS active in app state; it just isn't reflected in window.location until the next navigation. For the browser fallback, always construct the URL with all filter params explicitly rather than scripting clicks.

  • PUBLISHED_ASC sort is silently rejected and falls back to PUBLISHED_DESC. Other invalid sort values appear to be similarly silent — verify via metadata.sort in the response.

  • fuel is returned as a human-readable Swedish string in docs[i], not the enum id — e.g. "Plug-in Bensin", "Diesel", "El". The filter input still takes the integer id. Mapping the two sides requires the filters[name=fuel].filter_items[] table from any response.

  • location field in docs[i] is a free-text city/locality ("Stora Höga", "Hässelby"), not a structured region code. The structured region is only on the input side (location param, hierarchical 0.{länId} or 1.{länId}.{kommunId}). For agents that need län-level grouping on output, do the bucketing client-side using coordinates.lat/lon.

  • Cookie consent iframe is required only for the browser path, never for browse cloud fetch. The Cookieinställningar dialog (Vend / TCF v2 iframe) appears on first DOM visit; click "Godkänn alla" inside the iframe (a11y ref [2-1983] at the time of writing) — afterwards the euconsent-v2 cookie carries through the session.

  • Range slider boundaries from filters[name=*].min_value / max_value: price 0–700,000 kr (step 10,000), year 1950–2027 (step 1), mileage 0–20,000 mil (step 500). Sending values outside these clamps is accepted but silently capped.

  • No rate-limit observed, but Blocket's terms forbid systematic scraping (Innehållet skyddas av upphovsrättslagen…). Stay ≤ 1 req/s sustained.

  • The browser-trace skill conflicts with browse open on the same Browserbase session — both attach as CDP clients and only one can drive the page at a time. Either stop the trace capture before opening pages or skip trace capture for this skill (the API path doesn't need browser observability).

Expected Output

{
  "search": {
    "make":         "Volvo",
    "variant_code": "0.818",
    "price_from":   150000,
    "price_to":     300000,
    "year_from":    2018,
    "year_to":      null,
    "mileage_from_km": null,
    "mileage_to_km":   80000,
    "fuel":         "Diesel",
    "transmission": null,
    "region":       null,
    "sort":         "PRICE_ASC",
    "page":         1,
    "page_size":    20
  },
  "total_results": 1132,
  "page_count":    57,
  "listings": [
    {
      "ad_id": 22734541,
      "url":   "https://www.blocket.se/mobility/item/22734541",
      "title": "Volvo V60",
      "make":  "Volvo",
      "model": "V60",
      "model_specification": "Recharge T6 AWD Geartronic",
      "year": 2020,
      "mileage_km": 156800,
      "price_sek": 248000,
      "fuel": "Plug-in Bensin",
      "transmission": "Automatisk",
      "dealer_segment": "Privat",
      "dealer_name":   null,
      "regno": "GRJ17P",
      "vin":   "YV1ZWBFUDL1381720",
      "location": "Stora Höga",
      "lat":  58.02042,
      "lon":  11.81809,
      "image_url": "https://images.blocketcdn.se/dynamic/default/item/22734541/3cfef64a-408b-497b-86d9-6be434c3df43",
      "posted_at_iso": "2026-05-18T05:31:50.000Z"
    }
  ]
}

If the API returns zero matches:

{
  "search":        { "...": "..." },
  "total_results": 0,
  "listings":      []
}

If a filter was silently dropped (selected_filters[].display_name === raw value):

{
  "search":         { "...": "..." },
  "warning":        "Unrecognized filter value(s) dropped by Blocket: variant=0.7088",
  "total_results":  144153,
  "listings":       [ "..." ]
}
Blocket Car Search by Filters · browse.sh