contract 2026-10-05
Endpoints

Market intelligence

What a car is worth, and the price snapshots behind it. /api/market/valuation answers for a make and model worldwide and per country, from retail, auction, unsold and buy-now prices, with trends, time to sell and a price model’s estimate. The two snapshots cover a brand, model and year window — one for the Bulgarian retail market, one for our own auction inventory. Scope: market, granted per contract; none of the three is enabled on a new key by default. All three are read-only lookups into data built nightly or on the pipeline’s own schedule. On the two snapshots a 404 means "no snapshot exists for that window", the normal answer for a thin brand/model/year combination — not an error to retry — and neither snapshot route carries envelope metadata; read X-Request-ID from the header.

GET

/api/market/valuation

What a make and model is worth — retail, auction, unsold and buy-now prices, worldwide and per country.

Requires the market scope.

Parameters

brand / makestring · query stringrequired

Brand name, under either spelling.

Example BMW·Where the value comes from /api/brands -> brands[].name

modelstring · query stringrequired

Model name.

Example 320d·Where the value comes from /api/models?brand=bmw -> models[].name

yearinteger · query string

Exact registration year for the exact block; the range block widens it by year_flex.

Example 2019·Where the value comes from The car being valued.

year_flexinteger · query string

Years either side of year for the range block. Default 1, maximum 10.

Example 1·Where the value comes from How wide a range you want.

year_from / year_tointeger · query string

Range bounds when year is not given.

Example 2018 / 2021·Where the value comes from Year bounds chosen by your user.

fuelstring · query string

Plug-in and mild hybrids count as Hybrid.

Example Diesel·Where the value comes from Petrol, Diesel, Hybrid, Electric, LPG, CNG or Hydrogen.

gearboxstring · query string

Gearbox.

Example Automatic·Where the value comes from Automatic or Manual.

enginenumber · query string

Displacement in litres.

Example 2.0·Where the value comes from The car being valued.

engine_flexnumber · query string

Litres either side of engine for the range block. Default 0.2.

Example 0.2·Where the value comes from How wide a range you want.

conditionenum · query string

normal (default) excludes damaged lots, damaged keeps only them, any pools both. Classifieds are always normal.

Example normal·Where the value comes from normal, damaged or any.

mileageinteger · query string

Adds mileage_adjusted_eur — the median moved along the group’s own price/mileage slope, capped at ±35% — and sets the mileage of prediction.

Example 120000·Where the value comes from The car’s odometer in km.

countrystring · query string

Narrows exact, range, trend and time_to_sell. worldwide and countries always cover every country.

Example DE·Where the value comes from ISO 3166-1 alpha-2 country of the car.

kindsstring · query string

Comma-separated subset of price kinds to return.

Example retail,auction·Where the value comes from retail, auction, buy_now, unsold.

trend_monthsinteger · query string

Months of trend history including the current one. Default and maximum 12.

Example 12·Where the value comes from How much history to chart.

trend_weeksinteger · query string

Weeks of weekly trend history. Default 12, maximum 26.

Example 12·Where the value comes from How much history to chart.

Example requests

Request
GET https://api.thecarapi.com/api/market/valuation?brand=BMW&model=320d&year=2019&fuel=Diesel&mileage=120000
GET https://api.thecarapi.com/api/market/valuation?brand=Toyota&model=Corolla&year_from=2018&year_to=2021&country=DE&kinds=retail,auction

Example response

json
{
  "success": true,
  "valuation": {
    "vehicle": {
      "brand": "BMW",
      "model": "320d",
      "year": 2019,
      "year_from": 2018,
      "year_to": 2020,
      "fuel": "Diesel",
      "condition": "normal",
      "mileage_km": 120000,
      "country": null
    },
    "exact": {
      "retail": {
        "count": 640,
        "median_eur": 21900,
        "p25_eur": 19800,
        "p75_eur": 24100,
        "p10_eur": 17500,
        "p90_eur": 26900,
        "mean_mileage_km": 98000
      },
      "auction": {
        "count": 212,
        "median_eur": 16400,
        "p25_eur": 14100,
        "p75_eur": 18900,
        "p10_eur": 12200,
        "p90_eur": 21000,
        "mean_mileage_km": 131000
      }
    },
    "range": {},
    "worldwide": {},
    "countries": [
      {
        "country_code": "de",
        "retail": {
          "count": 410,
          "median_eur": 21200
        },
        "auction": {
          "count": 96,
          "median_eur": 16100
        },
        "count": 506,
        "time_to_sell": null
      }
    ],
    "ranking": {
      "retail": {
        "cheapest": [
          {
            "country_code": "pl",
            "count": 88,
            "median_eur": 18900
          }
        ],
        "most_expensive": [
          {
            "country_code": "ch",
            "count": 41,
            "median_eur": 27400
          }
        ]
      }
    },
    "trend": {},
    "time_to_sell": null,
    "price_bands": {},
    "prediction": {
      "estimates": {},
      "accuracy": {}
    },
    "methodology": {},
    "built": null
  },
  "contract_version": "2026-10-05"
}

What you get back

exact / rangeobject

Per price kind, a summary of count, median_eur, p10/p25/p75/p90_eur and mean_mileage_km (plus mileage_adjusted_eur when mileage is sent), or null when no car matched. Kinds: retail (classified asking prices in Europe, Korea and China), auction (closing price of finished lots — the published result where the source has one, else the last bid seen), unsold (closing prices the car did not sell at) and buy_now (B2B buy-now prices). exact applies year and engine as given; range widens them.

worldwide / countries / rankingobject

worldwide is the same summary across every country; countries[] has count and median per kind for each country_code; ranking.<kind> lists the cheapest and most_expensive markets (countries with at least 5 observations).

trendobject

Per kind: weekly and change_pct (latest four weeks against the four before), monthly and monthly_change_pct (latest three months against the three before).

time_to_sellobject | null

Overall and per country, for retail and auction: Kaplan-Meier median days to sell (median_days, or over_days while over half are unsold), sold_within_pct within sold_within_days, sold and listings. Retail measures until a classified left the market, which is not proof of a sale. Null below 5 sales.

price_bandsobject

For retail and auction, five bands around that kind’s median (under -15%, -15..-5%, ±5%, +5..+15%, over +15%) with the time to sell at that price, and for auctions closes and sold_pct.

predictionobject

A price model’s estimate, available even where few or no cars match: estimates.<kind> with estimate_eur, range_50_eur and range_80_eur (where an individual car’s price falls), basis (model, blended or comparables), per_year_older_pct, per_10k_km_pct, by_mileage and by_year. accuracy.<kind> reports held-out test error.

builtobject | null

When the nightly build that answered this ran.

GET

/api/cars-bg-market

Cars.bg Bulgarian retail market snapshot.

Requires the market scope.

Parameters

brand / makestring · query stringrequired

Brand name. Either spelling. Resolved through a fuzzy brand matcher, so display names work.

Example BMW·Where the value comes from /api/brands -> brands[].name

modelstring · query stringrequired

Model name. Resolved to one or more catalogue model ids.

Example 320d·Where the value comes from /api/models?brand=bmw -> models[].name

yearinteger · query stringrequired

The centre year.

Example 2019·Where the value comes from Centre year chosen by your application.

flexinteger · query string

Year tolerance ± (0–10, default 1). A value outside that range is a 400.

Example 2·Where the value comes from Tolerance chosen by your application.

Example requests

Request
GET https://api.thecarapi.com/api/cars-bg-market?brand=BMW&model=320d&year=2019
GET https://api.thecarapi.com/api/cars-bg-market?make=Audi&model=A4&year=2020&flex=2

Example response

json
{
  "success": true,
  "snapshot": {
    "brand_id": "12",
    "model_ids": [
      "4411"
    ],
    "year_center": 2019,
    "year_flex": 2,
    "year_from": 2017,
    "year_to": 2021,
    "listing_count": 84,
    "avg_price_eur": 24310.5,
    "median_price_eur": 23900,
    "min_price_eur": 15500,
    "max_price_eur": 41000,
    "offers": [],
    "raw_matches": [],
    "computed_at": "2026-08-27T02:14:11",
    "updated_at": "2026-08-27T02:14:11"
  }
}

What you get back

year_from / year_tointeger

The window year ± flex actually resolved to.

listing_countinteger

How many retail listings the aggregate is built from. Read this first — a median over four cars is not a market price.

avg_price_eur / median_price_eur / min_price_eur / max_price_eurnumber

EUR aggregates as JSON numbers. Prefer the median; retail asking prices have a long right tail.

offersobject[]

The listings behind the aggregate.

raw_matchesobject[]

Everything the model matcher considered, including rows the aggregate excluded. Useful for auditing a surprising number.

computed_at / updated_attimestamp

When the snapshot was built. It is not recomputed per request.

GET

/api/auction-market

Auction-market price snapshot for the same brand, model, and year window.

Requires the market scope.

Parameters

brand / makestring · query stringrequired

Brand name. Matched against the normalized clean_make.

Example BMW·Where the value comes from /api/brands -> brands[].name

modelstring · query stringrequired

Model name. Matched against clean_model.

Example 320d·Where the value comes from /api/models?brand=bmw -> models[].name

yearinteger · query stringrequired

The centre year.

Example 2019·Where the value comes from Centre year chosen by your application.

flexinteger · query string

Year tolerance ± (0–10, default 1).

Example 2·Where the value comes from Tolerance chosen by your application.

scopeenum · query string

Default all. Anything else is a 400. Each is a separately built snapshot, so active can exist while inactive does not.

Example active·Where the value comes from Choose all, active, or inactive.

Example requests

Request
GET https://api.thecarapi.com/api/auction-market?brand=BMW&model=320d&year=2019&scope=active
GET https://api.thecarapi.com/api/auction-market?brand=Kia&model=EV6&year=2023&flex=1

Example response

json
{
  "success": true,
  "snapshot": {
    "clean_make": "BMW",
    "clean_model": "320d",
    "year_center": 2019,
    "year_flex": 1,
    "year_from": 2018,
    "year_to": 2020,
    "scope": "all",
    "auction_count": 412,
    "avg_price_eur": 18220.4,
    "median_price_eur": 17800,
    "p10_price_eur": 11900,
    "p25_price_eur": 14750,
    "p75_price_eur": 21400,
    "p90_price_eur": 26100,
    "min_price_eur": 6200,
    "max_price_eur": 44900,
    "first_seen_min": "2026-01-04T00:00:00",
    "first_seen_max": "2026-08-22T00:00:00",
    "auctions": [],
    "computed_at": "2026-08-27T02:41:03",
    "updated_at": "2026-08-27T02:41:03"
  }
}

What you get back

scopeenum

Echoes the request: active restricts to lots still open, inactive to closed ones, all covers both.

auction_countinteger

Lots behind the aggregate. Sanity-check it before quoting a percentile.

p10_price_eur / p25_price_eur / p75_price_eur / p90_price_eurnumber

Percentiles — the reason to prefer this over the Cars.bg snapshot when you want a spread rather than a point estimate. A wide p10–p90 band means the specification is not pinning the price down.

first_seen_min / first_seen_maxtimestamp

The observation window the lots span. A snapshot whose window is eight months wide is not a spot price.

auctionsobject[]

The individual lots the aggregate was computed from.