Live prices
Cycle prices versus the at-request bid read on running auctions, and how to read live_price and live_price_pending.
Two kinds of price, and it is worth knowing which one you are holding. Stored specifications, condition, availability and prices reflect the latest successful refresh of those fields. A fixed-price listing can also change upstream between refreshes; a cycle price is the latest observation, not a guarantee of current availability or a purchase quote.
The exception is a running auction with open bidding, where the figure can move any time somebody bids. Eligible OpenLane and eCarsTrade lots receive a live bid overlay on detail requests. A recently fetched bid is reused within the live-price TTL. A cold refresh may still be pending or fail, leaving the stored price in place.
| Cycle price | Live price | |
|---|---|---|
| What it is | The listing as of the last refresh | A recent bid fetched from the auction house, possibly reused from the live-price cache |
| Covers | Every listing on every source | Running open-bidding auctions on openlane and ecarstrade |
| Age | Inspect listing timestamps; data_updated_at may default to response time | Inspect fetched_at and ttl_seconds |
| Where you see it | Search, facets, top offers, market data, and any detail response that did not qualify | The two detail endpoints only |
| How you can tell | No live_price block on the response | The response carries a live_price block |
| Reused for | Until the next refresh | About two minutes (live_prices.ttl_seconds) |
There is no separate real-time endpoint or query parameter to switch on. Ask for a car's details the way you already do. If it qualifies, the response carries a live_price block; if it does not, the cycle price is what exists.
Which sources
| Source | Live prices | Why |
|---|---|---|
openlane | Yes | Timed online auction with open bidding. |
ecarstrade | Yes | Timed online auction; on an open auction the current bid is only published over the auction house's socket. Blind eCarsTrade auctions are never refreshed — they publish no bid anywhere by design. |
auto1, encar, copart, schadeautos, kbchachacha, japan | No | The current implementation has no live-price overlay for these sources. Prices reflect the latest successful stored observation. |
Do not hardcode that list — read live_prices.sites from GET /api/contract.
Reading the response
When a price was refreshed, the detail body gains:
"live_price": {
"price": 25900,
"currency": "EUR",
"source": "openlane",
"fetched_at": 1786659750
}price is the raw bid at the auction house. fetched_at is a Unix timestamp in seconds — use it, not your own clock, to render an "as of" indicator. At the same time the existing price fields are overwritten with figures recomputed from that bid, on both detail endpoints, so a client that ignores live_price entirely still shows the right number.
| Field | Meaning |
|---|---|
current_price | The raw current bid. Equals live_price.price. |
current_final | Bid plus fees and taxes. |
current_tax, current_tax_delivery | The recomputed tax components. |
public_price_eur | The headline price, recomputed. |
buy_now_price, buynow_final | Not touched — a buy-now figure does not move with bidding. |
Where the block sits
Both detail endpoints publish the same price fields, under the same names, with the same JSON types, and a live refresh rewrites them the same way on either one. The only difference is nesting, and it follows the shape each endpoint already had.
// /api/auction/{site_slug}/{auction_id} — wrapped in "auction"
{ "success": true,
"auction": { "auction_id": 11409652,
"current_price": 25900,
"current_final": 33566,
"public_price_eur": 33926,
"live_price": { "price": 25900, "currency": "EUR" } } }
// /api/car-details — source-shaped, so the same keys sit at the top level
{ "success": true, "site": "openlane", "auction_id": 11409652,
"current_price": 25900,
"current_final": 33566,
"public_price_eur": 33926,
"auction": { "extracted_fields": { "images": [] } },
"live_price": { "price": 25900, "currency": "EUR" } }Price fields are JSON numbers
Prices are JSON numbers on both endpoints, whether or not a live refresh happened. Before contract 2026-08-18 they were serialized as strings on a normal read but as numbers whenever a live price had been refreshed — the same field, two types, switching on something a client could neither control nor predict.
- "current_price": "25600.00"
+ "current_price": 25600.0Nothing to do if you parse with Number(...), float(...) or into a dynamic type, or if your client already tolerated the live-price case. A statically typed client that declares these as strings has to retype them as numbers.
start_price current_price current_final
current_tax current_tax_delivery buy_now_price
buynow_final buynow_tax buynow_tax_delivery
final_price public_price_eur current_bgn
buynow_bgnWhen there is no live price
The live_price block is absent whenever the price was not refreshed. That covers several different situations, and apart from the two flagged below they are indistinguishable in a detail response:
- The source is not OpenLane or eCarsTrade, the two supported live-price sources.
- The auction has already ended.
- The car has no bids yet. Common on eCarsTrade, where a car with no current bid publishes no price at all.
- The auction is blind. Most eCarsTrade auctions are: every bid is private, so no price is published and none is inferred.
current_pricestaysnulland that is the correct, final answer — not a missing value to retry for. This case you can identify: the detail response carriesis_blind: true, and it is the one reliable way to tell "there will never be a price" from "there is no price right now". - Live prices are unavailable on this deployment.
- The refreshed figure did not arrive in time for this response. This one is different, and the response tells you so — see
live_price_pendingbelow.
Its absence never means the stored price is wrong. It means you are looking at a cycle price, which is the normal state for the overwhelming majority of listings. To tell "live prices are unavailable" from "this car has none right now", read live_prices.enabled from /api/contract once at startup; where the feature does not exist at all, the whole live_prices block is missing.
live_price_pending: the refresh that arrived late
Your request is never held waiting on the auction house. When a current figure is not already to hand, the response comes back immediately with the last known price plus live_price_pending: true, and the refreshed figure is ready a few seconds later. Nothing is lost; it just missed your response.
| Response carries | What you are holding | What to do |
|---|---|---|
live_price | The current bid | Nothing. Render it. |
live_price_pending: true | The last known price; the refresh is still running | Repeat the same request once after ~2s. The repeat is served from cache. |
| Neither | The cycle price, and that is what exists | Nothing. This is the normal case. |
Use the flag to schedule one delayed follow-up read. If a bid is still unavailable, keep the last observed price with its timestamp rather than polling indefinitely; a later user-driven refresh may succeed. A pending response is sent with Cache-Control: no-store and X-Live-Price: pending, so a browser, CDN or shared proxy cannot answer your follow-up with the very body that missed the price.
async function vehicleWithLivePrice(site, id) {
const first = await getJson(`/api/auction/${site}/${id}`);
if (first.auction.live_price || !first.auction.live_price_pending) return first;
// The refresh missed the budget but is still running. Exactly one more read.
await new Promise((r) => setTimeout(r, 2000));
return getJson(`/api/auction/${site}/${id}`);
}Polling
A refreshed price stays current for live_prices.ttl_seconds (default 120), and Cache-Control: max-age on the response matches. Polling faster than that returns the same figure and gains you nothing — honour max-age. Note this is per read, not a subscription: asking for the car once gives you the bid at that moment; it does not keep tracking while you hold the page. A bid that moves afterwards appears on your next read once the TTL has passed.
What movement to expect
A live bid may differ from the last stored observation. The API does not promise a maximum difference; use the returned value and fetched_at timestamp. Treat live_price as a correction, not a stream — it is the right thing to read when a user opens a vehicle, and the wrong thing to poll in a loop.
What stays on cycle prices
Only the detail path is live. /api/search, /api/top-offers, the market intelligence endpoints and every price-based sort or filter read cycle prices.
Everything here is read only. This API observes auction prices; it never places, raises or withdraws a bid on your behalf.