---
title: "Auctions & history"
description: "Retrieve source-aware listing detail, galleries, price changes, and VIN history. A running openlane or ecarstrade auction has its bid refreshed from the auction house while the request is served — nothing needs to be passed to enable it."
canonical: "https://thecarapi.com/docs/auctions"
contract_version: "2026-08-19"
api_base: "https://api.thecarapi.com"
source: "https://thecarapi.com/docs/auctions.md"
---

# Auctions & history

Retrieve source-aware listing detail, galleries, price changes, and VIN history. A running openlane or ecarstrade auction has its bid refreshed from the auction house while the request is served — nothing needs to be passed to enable it.

## GET /api/auction/{site_slug}/{auction_id}

Canonical auction detail with private and bidder fields removed.

**Scope:** `auctions`

### Parameters

| Parameter | Send in | Type | Required | Example | Where the value comes from | Description |
| --- | --- | --- | --- | --- | --- | --- |
| `site_slug` | URL path | string | yes | `encar` | /api/search -> results[].site_name | Source name returned by /api/sites. |
| `auction_id` | URL path | integer | yes | `38112900` | /api/search -> results[].auction_id | Integer auction database id. |

### Example requests

```http
GET https://api.thecarapi.com/api/auction/encar/38112900
```

```http
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
    }
  }
}
```

### Response fields

| Field | Type | Meaning |
| --- | --- | --- |
| `vault_gallery` | object | 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_details` | object | Cross-source normalized condition and paperwork: documents, inspection_reports, option_reports, service_history, technical_inspection, paperwork, condition, damages, equipment, warranty, specs, location, seller. Every key is omitted when the source has nothing for it, and the whole block is omitted while details_pending is true. |
| `car_identification` | object | 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_images` | object[] | 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_price` | object | Present only when the bid was refreshed from the auction house during this request: { 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 never means the price is stale — every listing is live-priced from the continuous feed regardless. The block only reports the additional at-request bid re-read, which most listings do not qualify for. |
| `live_price_pending` | boolean | True when the upstream refresh missed the request budget and is still running. 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. |
| `current_price / current_final / current_tax / current_tax_delivery / public_price_eur` | number | Recomputed from the refreshed 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_end_at` | timestamp \| 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_eur` | number \| 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 null for roughly 86% of eCarsTrade lots. 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. |
| `steering` | enum | left or right, never null. japanauction 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. |
| `co2` | integer \| 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_estimated` | integer \| 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_standard` | enum \| 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. |

**Notes.** Live prices: for a running openlane or ecarstrade auction the bid is refreshed from the source while this request is served, the price fields are rewritten and a live_price block is attached. Nothing enables it and there is no separate real-time endpoint. Every other listing is still live-priced from the continuous feed — it simply carries no separate bid to re-read. A refreshed price is reused for ttl_seconds (default 120) and Cache-Control: max-age drops to match, so polling faster gains nothing. Read live_prices from /api/contract rather than hardcoding which sources qualify. This API only observes auction prices — it never places, raises or withdraws a bid. Returns 404 when the source-aware listing does not exist. Auction fees, bid history, transport and every delivery*/selfpickup* key are removed at the response boundary — they are quoted to our buyer account and our delivery address, not yours. Published asking prices, VAT scheme, estimated repair cost and seller location are unaffected; for landed cost use the import calculator, which prices your own route.

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

Chronological source-price and public-price snapshots for a listing.

**Scope:** `auctions`

### Parameters

| Parameter | Send in | Type | Required | Example | Where the value comes from | Description |
| --- | --- | --- | --- | --- | --- | --- |
| `site_slug` | URL path | string | yes | `encar` | /api/search -> results[].site_name | Auction source slug. |
| `auction_id` | URL path | integer | yes | `38112900` | /api/search -> results[].auction_id | Auction database id. |

### Example requests

```http
GET https://api.thecarapi.com/api/auction/encar/38112900/price-history
```

```http
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": "price_change",
      "changed_fields": [
        "current_price"
      ],
      "current_price": 21500,
      "public_price_eur": 21500,
      "observed_at": "2026-07-10T08:00:00"
    }
  ]
}
```

## 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.

**Scope:** `auctions`

### Parameters

| Parameter | Send in | Type | Required | Example | Where the value comes from | Description |
| --- | --- | --- | --- | --- | --- | --- |
| `site_slug` | URL path | string | yes | `encar` | /api/search -> results[].site_name | Auction source slug. |
| `auction_id` | URL path | integer | yes | `38112900` | /api/search -> results[].auction_id | Auction database id. |

### Example requests

```http
GET https://api.thecarapi.com/api/auction-images/encar/38112900
```

```http
GET https://api.thecarapi.com/api/auction-images/openlane/11125938
```

### Example response

```json
{
  "success": true,
  "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
    }
  ]
}
```

### Response fields

| Field | Type | Meaning |
| --- | --- | --- |
| `pending` | integer | How many photos the auction still owes. Show placeholders for these instead of a silently short gallery. A non-zero value also promotes the outstanding rows to user-requested priority. |
| `url` | string | The best URL available right now — served_url once vaulted, remote_url until then. |
| `served_url` | string | Same-origin vault path once the photo is stored. Load this directly. |
| `remote_url` | string | Original source URL. Load this through an image proxy. |
| `image_status` | enum | ready, pending, downloading, failed, or not_downloaded. |
| `image_source` | enum | downloaded (in the vault) or proxied (still served from the source). |
| `index / is_primary` | integer / boolean | Gallery order and which photo is the thumbnail. |
| `source_section` | string | Section the source filed the photo under, e.g. exterior, interior, damage. |
| `width / height` | integer | Pixel dimensions of the stored photo. |

**Notes.** You usually do not need this call: /api/auction/{site}/{id} embeds this same body as vault_gallery, so a detail page can be one request rather than two. This endpoint stays fully supported and is still the right one when you want the gallery on its own; both are served from a single cache entry, so they never disagree, and while pending is non-zero both carry max-age=10. Every entry carries a URL that works right now. Stored photos are AVIF or WebP depending on what the source served — rely on the URL and content type, not the extension. An empty images array means nothing is stored yet; an invalid site returns 404.

## GET /api/vin/{vin}/history

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

**Scope:** `auctions`

### Parameters

| Parameter | Send in | Type | Required | Example | Where the value comes from | Description |
| --- | --- | --- | --- | --- | --- | --- |
| `vin` | URL path | string | yes | `WBA8E9G50GNU12345` | /api/auction/{site}/{id} -> auction.chassis_number | Valid 17-character VIN. Exact match only. |

### Example requests

```http
GET https://api.thecarapi.com/api/vin/WBA8E9G50GNU12345/history
```

```http
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
    }
  ]
}
```

**Notes.** Returns up to 100 matches, newest first. An invalid VIN returns 400.
