contract 2026-09-03
Endpoints

Auctions & history

Retrieve source-aware listing detail, galleries, price changes, and VIN history. A running openlane or ecarstrade auction has its bid read from the auction house at the moment you request the detail — nothing needs to be passed to enable it. Every other listing is served at its cycle price, refreshed by the scheduled ingestion pipeline.

GET

/api/auction/{site_slug}/{auction_id}

Canonical auction detail with private and bidder fields removed.

Requires the auctions scope.

Parameters

site_slugstring · in the URL pathrequired

Source name returned by /api/sites.

Example encar·Where the value comes from /api/search -> results[].site_name

auction_idinteger · in the URL pathrequired

Integer auction database id.

Example 38112900·Where the value comes from /api/search -> results[].auction_id

Example requests

Request
GET https://api.thecarapi.com/api/auction/encar/38112900
GET https://api.thecarapi.com/api/auction/openlane/11125938

Example response

json
{
  "success": true,
  "auction": {
    "auction_id": 11409652,
    "site_name": "openlane",
    "clean_make": "BMW",
    "clean_model": "320d",
    "model_display": "320d M Sport",
    "registration_year": 2020,
    "mileage": 45000,
    "current_price": 25900,
    "current_final": 33566,
    "public_price_eur": 33926,
    "auction_end_at": "2026-08-20T10:00:00Z",
    "images": [],
    "vault_gallery": {
      "images": [
        {
          "served_url": "/image-vault/ab/cd/openlane_11409652_00_deadbeef.avif",
          "remote_url": "https://cdn.example/photo_1.jpg",
          "image_status": "ready"
        }
      ],
      "count": 28,
      "pending": 0
    },
    "vehicle_details": {},
    "car_identification": {},
    "live_price": {
      "price": 25900,
      "currency": "EUR",
      "source": "openlane",
      "fetched_at": 1786659750
    }
  }
}

What you get back

vault_galleryobject

The image vault’s authoritative photo set, embedded verbatim: the exact body of /api/auction-images/{site}/{id} ({ images, count, pending }). Read it and a detail page is one request instead of two. Both routes build it from one cache entry, so they can never disagree; while pending is non-zero this response is served with max-age=10 so a polling client sees photos as they land. A client that ignores it behaves exactly as before.

vehicle_detailsobject

Cross-source normalized condition and paperwork: documents, inspection_reports, option_reports, car_reports, service_history, technical_inspection, paperwork, condition, damages, equipment, warranty, specs, location, seller, profile. Every key is omitted when the source has nothing for it, and the whole block is omitted for a listing whose details have not been fetched yet (details_pending: true).

car_identificationobject

Raw source-specific spec map. Prefer vehicle_details where it covers what you need — its keys are stable across sources, these are not.

images / gallery_imagesobject[]

What the source record itself carried — untouched by this release. For the vault’s photo set with served URLs and dimensions, read vault_gallery above.

live_priceobject

Present when a live bid overlay is available, including a recent cached bid within the TTL: { price, currency, source, fetched_at }. price is the raw bid and equals current_price; fetched_at is a Unix timestamp in seconds — use it, not your own clock, to render “as of”. Its absence is not an error: it means the car is served at its cycle price, which for most listings is the only price there is.

live_price_pendingboolean

True when the refreshed figure was not to hand in time for this response — your request is never held waiting on the auction house. Read the car once more after ~2s and then stop; the second read is served from cache. These responses carry Cache-Control: no-store and X-Live-Price: pending. This is the only case worth reading again for, and the only case that sets the flag.

current_price / current_final / current_tax / current_tax_delivery / public_price_eurnumber

JSON numbers, always. Recomputed from the bid when a live price happened, so a client that ignores live_price entirely still shows the right number. buy_now_price and buynow_final are never touched — a buy-now figure does not move with bidding.

auction_id_strstring

The auction id as a string — the one to persist and to address the car with. japan ids exceed 2^53, so JavaScript and any other client parsing JSON numbers as IEEE-754 doubles silently rounds the numeric auction_id and the result no longer addresses the row it came from. auction_id is unchanged and stays a number for existing consumers.

vehicle_id / manufacturer_slug / model_group_slug / fuel_typeinteger | string

The same four catalog-shaped keys /api/listVehicles adds. vehicle_id is a duplicate of auction_id (same 2^53 caveat — prefer auction_id_str). manufacturer_slug and model_group_slug are the values the catalog routes are addressed by, so a detail response is enough to build a link to its manufacturer and model-group pages without a second lookup. fuel_type is present only when the row carries a fuel.

is_blindboolean

True when the auction house publishes no bid at all, by design — most eCarsTrade auctions. This is the one case where current_price: null is a final answer rather than missing data: there is no figure to fetch, now or later, and no live price will ever arrive. Read it before rendering "price unavailable, retrying…", which on a blind lot retries forever; show estimated_value_eur instead, labelled as an estimate.

details_pendingboolean

True while the full detail payload has not been fetched from the source yet. vehicle_details is omitted meanwhile and the response carries a short max-age so a polling client sees the details land. Not an error.

auction_end_attimestamp | null

Absolute UTC instant the lot closes, or null for stock with no deadline. Drive countdowns from this — not from batch_end_date, whose timezone differs per auction house, nor auction_sec_left, which is a snapshot taken at the last refresh rather than seconds from now.

estimated_value_eurnumber | null

The auction house own valuation of the vehicle, in EUR — and NOT a price you can pay. No bid is settled against it and no car is sold at it. Only ecarstrade publishes one (its "eCT Estimation Price"); it is null for every other source and may be null on eCarsTrade lots without a valuation. Where it does appear it is often the only number on the lot, because a blind auction hides the bid: current_price, start_price and buy_now_price are all null while this carries the estimate. Label it as an estimate wherever you show it, and never sort or filter on it — it is absent on most cars, so any ordering that uses it silently drops them.

steeringenum

left or right, never null. japan is the only source that publishes a steering field; for every other source the side is read off the car registration market — a car registered in a country that drives on the left (GB, IE, JP, CY, MT, AU, NZ, ZA, IN, TH, HK, SG) is right, everything else left. Also present on every search result card and on /api/car-details.

co2integer | null

CO2 in g/km as the auction house measured and published it. Null on roughly half of lots, which publish none. It carries no standard tag and mixes both EU test cycles by registration year — see the CO2 & emissions page.

co2_estimatedinteger | null

A derived CO2 figure for lots that publish none, inferred from other lots of the same specification. NEVER a measured value, and null wherever the specification does not decide it. It is a separate field from co2 on purpose, so an estimate can never be mistaken for a measurement.

co2_estimated_standardenum | null

NEDC or WLTP — which EU test cycle co2_estimated is expressed on. Always read the estimate together with this: the same physical car reads 12-25% higher under WLTP, so a figure without its standard is not comparable.

vin_ocrobject | null

Auto1 only, and the reason an Auto1 car has a VIN at all: Auto1 publishes no chassis number but photographs the registration certificate, so the VIN is read off that paperwork. { vin, confidence, evidence } — confidence is high, stored or medium, and evidence identifies the document photo it was read from. A high or stored read also fills chassis_number when the row has none, so a client that only reads chassis_number needs no change. A medium read is labelled and never promoted to the car identity: show it as unconfirmed. Roughly 41% of Auto1 cars with a document photo end up with a published VIN — the ceiling is upstream, because Auto1 blurs the VIN field on ES, IT and PT certificates. Part of the vehicle_identity field grant.

vin_ocr_pendingboolean

True when a VIN read was queued by this request and was not finished in time to answer it — your request is never held waiting on OCR. Read the car once more after a second or two and then stop; the answer is cached for a day once found. Behaves exactly like live_price_pending, and like it, is never an error.

source_comparisonobject | null

Korean sources only. The same car is often listed on both Encar and KB ChaChaCha; listings are grouped on a shared VIN, or a corroborated plate with make and model, and one is published while the rest are hidden. This block is the evidence: offers (every listing for the car, each with site, auction_id, price_eur, offer_link and eligible), selected (the one published), spread_eur (highest minus lowest eligible price — what the same car costs on the cheaper source), image_source (the listing the gallery came from; photo sets from two listings are never interleaved) and match_method (vin or plate_make_model). Requires the korea_merge field grant.

canonical_offerobject | null

Present on a Korean listing that was resolved as a duplicate: { site, auction_id } for the representative published in its place. Also behind the korea_merge field grant.

GET

/api/auction/{site_slug}/{auction_id}/price-history

Every recorded price movement for one listing, oldest first.

Requires the auctions scope.

Parameters

site_slugstring · in the URL pathrequired

Auction source slug.

Example encar·Where the value comes from /api/search -> results[].site_name

auction_idinteger · in the URL pathrequired

Auction database id.

Example 38112900·Where the value comes from /api/search -> results[].auction_id

Example requests

Request
GET https://api.thecarapi.com/api/auction/encar/38112900/price-history
GET https://api.thecarapi.com/api/auction/openlane/11125938/price-history

Example response

json
{
  "success": true,
  "site": "encar",
  "auction_id": 38112900,
  "source_auction_id": "38112900",
  "history": [
    {
      "event_type": "initial",
      "source_auction_id": "38112900",
      "changed_fields": [
        "current_price",
        "public_price_eur"
      ],
      "current_price": 21500,
      "buy_now_price": null,
      "start_price": null,
      "final_price": null,
      "current_final": null,
      "buynow_final": null,
      "public_price_eur": 21500,
      "currency_code_id": "EUR",
      "observed_at": "2026-07-10T08:00:00",
      "created_at": "2026-07-10T08:00:05"
    }
  ]
}

What you get back

event_typeenum

Exactly three values: initial (the first prices we ever recorded for this lot), baseline (the state before the first observed change, written once so a series never starts mid-move), and change (a subsequent movement). There is no price_change value — a client matching on that has been discarding every event.

changed_fieldsstring[]

Which price fields moved in this event. Always an array, empty on a baseline row.

current_price / buy_now_price / start_price / final_price / current_final / buynow_final / public_price_eurnumber | null

The full price snapshot as of observed_at. JSON numbers or null.

currency_code_idstring

Currency of the source-side figures.

observed_attimestamp

When the change was observed. The field to plot against.

created_attimestamp

When the row was written. Within seconds of observed_at; not a second data point.

GET

/api/auction-images/{site_slug}/{auction_id}

Ordered gallery metadata backed by the image vault. Usually unnecessary — the same body rides on the auction detail response as vault_gallery.

Requires the auctions scope.

Parameters

site_slugstring · in the URL pathrequired

Auction source slug.

Example encar·Where the value comes from /api/search -> results[].site_name

auction_idinteger · in the URL pathrequired

Auction database id.

Example 38112900·Where the value comes from /api/search -> results[].auction_id

Example requests

Request
GET https://api.thecarapi.com/api/auction-images/encar/38112900
GET https://api.thecarapi.com/api/auction-images/openlane/11125938

Example response

json
{
  "success": true,
  "count": 1,
  "pending": 12,
  "images": [
    {
      "url": "https://cdn.example/photo_1.jpg",
      "remote_url": "https://cdn.example/photo_1.jpg",
      "served_url": "/image-vault/ab/cd/ecarstrade_7399555_00_deadbeef.avif",
      "thumbnail": "/image-vault/ab/cd/ecarstrade_7399555_00_deadbeef.avif",
      "image_status": "ready",
      "image_source": "downloaded",
      "index": 0,
      "is_primary": true,
      "source_section": "exterior",
      "width": 1024,
      "height": 768
    }
  ]
}

What you get back

countinteger

How many photos this response carries.

pendinginteger

How many photos the auction still owes. Show placeholders for these instead of a silently short gallery.

urlstring

The best URL available right now — served_url once vaulted, remote_url until then.

served_urlstring

A path, not an absolute URL. It begins /image-vault/… and is relative to the API base, so join it: https://api.thecarapi.com + served_url. Load this directly once the photo is stored.

remote_urlstring

Always absolute — the source’s own URL. Load this through an image proxy until the photo is vaulted.

image_statusenum

ready, pending, downloading, failed, blocked, or not_downloaded — but on this endpoint it is always ready, because only stored photos are listed at all. The other values appear on the image annotations attached to search cards, where a photo can still be in flight. failed and blocked mean the photo exhausted its retries and is not counted in pending, so a gallery can settle with pending: 0 and still be shorter than picture_count.

image_sourceenum

downloaded (in the vault) or proxied (still served from the source).

index / is_primaryinteger / boolean

Gallery order and which photo is the thumbnail.

source_sectionstring

Section the source filed the photo under, e.g. exterior, interior, damage.

width / heightinteger

Pixel dimensions of the stored photo.

servedUrl / thumbnailUrl / image_indexstring | integer

camelCase and legacy twins of served_url, thumbnail and index, kept for older clients and always equal to their counterparts. Prefer the snake_case names — the twins may be retired.

local_status / local_rel_pathstring

local_status is the same value as image_status; local_rel_path is the vault path served_url is built from.

GET

/api/vin/{vin}/history

Look up a full VIN across current and archived auction records.

Requires the auctions scope.

Parameters

vinstring · in the URL pathrequired

Valid 17-character VIN. Matching ignores punctuation and case in the stored VIN, so a source that wrote it with separators still matches.

Example WBA8E9G50GNU12345·Where the value comes from /api/auction/{site}/{id} -> auction.chassis_number

Example requests

Request
GET https://api.thecarapi.com/api/vin/WBA8E9G50GNU12345/history
GET https://api.thecarapi.com/api/vin/KNAB3811ALT123456/history

Example response

json
{
  "success": true,
  "vin": "WBA8E9G50GNU12345",
  "match_count": 2,
  "auctions": [
    {
      "site_name": "encar",
      "auction_id": 38112900,
      "clean_make": "BMW",
      "clean_model": "320d",
      "mileage": 45000,
      "public_price_eur": 21500,
      "first_seen_at": "2026-06-01T00:00:00",
      "last_seen_at": "2026-07-10T00:00:00",
      "archived": false,
      "source_auction_id": "38112900"
    }
  ]
}

What you get back

site_name / auction_id / source_auction_idstring | integer

Which source and lot. source_auction_id is the Auto1 batch id where one exists, otherwise the auction id as text.

chassis_numberstring

The VIN as the source published it (punctuation and case as stored).

clean_make / clean_model / model_displaystring

Normalized identity.

date_first_registration / mileagestring | integer

Registration date and the mileage recorded on that appearance.

current_price / buy_now_price / start_price / final_price / current_final / buynow_final / public_price_eurnumber | null

The whole price block. JSON numbers or null.

currency_code_idstring

Currency of the source-side figures.

first_seen_at / last_seen_at / last_available_at / created_at / updated_at / archived_attimestamp | null

Lifecycle. archived_at is null on a live row.

is_active / offer_linkboolean | string

Whether the lot is still open, and its listing URL.

archivedboolean

True when this appearance comes from the archive rather than live inventory.