Recipes
Six end-to-end flows that cover most of what the API is for. Each is a complete sequence, not a fragment.
Set API=https://api.thecarapi.com and KEY=your_key first.
1. Build a filter sidebar in one request
Six dimensions, one round trip, one unit of quota:
curl -sS -H "X-API-Key: $KEY" --compressed \
"$API/api/facets?fields=brands,years,fuels,gearboxes,countries,sites"Then narrow it as the user picks. Facets cross-filter: pass the filters already chosen and every remaining dimension re-counts against them, while each dimension still ignores its own filter so the user can change their mind.
# After "BMW, under EUR 15,000": what fuels and years are left?
curl -sS -H "X-API-Key: $KEY" \
"$API/api/facets?fields=fuels,years&brand=bmw&price_to=15000"Models are per-brand and stay on their own route:
curl -sS -H "X-API-Key: $KEY" "$API/api/models?brand=bmw&ordering=-count&limit=25"Cache the sidebar. Facets are served max-age=600 with an ETag; store it and send If-None-Match and the refresh costs an empty 304.
2. Search, page it, and keep the totals honest
curl -sS -H "X-API-Key: $KEY" --compressed \
"$API/api/search?brand=bmw&fuel=Diesel&year_from=2018&sort=price_low&limit=50&page=1"- Paging. Pick one spelling and stay with it —
page+page_size, oroffset+limit. Mixing them is a400. Loop tototal_pages; a filtered search'stotaland its rows now agree, so you will not run into short pages. - Counting is the expensive half. If you are streaming rather than showing a page count, send
include_total=falseand skip it entirely. If you want only the number, sendcount_only=trueand get no rows. - Persist
site_name+auction_id_str, neverauction_idon its own — japan ids exceed 2^53.
# "How many diesel BMWs are there?" — one cheap call, no rows.
curl -sS -H "X-API-Key: $KEY" \
"$API/api/search?brand=bmw&fuel=Diesel&count_only=true" | jq .total3. Open one car: detail, live bid, photos
One request gets specification, condition and gallery together:
curl -sS -D headers.txt -H "X-API-Key: $KEY" --compressed \
"$API/api/auction/openlane/11409652" -o car.jsonThen branch on three things, in this order:
live_price present -> that IS the current bid; render it
live_price_pending present -> read once more after ~2s, then stop
neither -> the cycle price is the price; do not retry
is_blind: true -> there will never be a price. Show estimated_value_eur,
labelled as the auction house's estimate.
details_pending: t -> vehicle_details is not there yet. Re-read on the short
max-age the response carries; render the rest meanwhile.
vault_gallery.pending > 0 -> more photos are coming. Poll on max-age=10.Photo URLs are paths, so join them to the base:
jq -r '.auction.vault_gallery.images[] | .served_url // .remote_url' car.json \
| sed "s|^/|$API/|"You do not need /api/auction-images as well — vault_gallery is that same body embedded. Use the separate route only when you want the gallery alone.
4. Shop the discounts, then price the import
With the market_reference field grant, top offers carry the reference used for their scoring:
curl -sS -H "X-API-Key: $KEY" \
"$API/api/top-offers?site=openlane&min_savings_pct=20&sort=savings&limit=10"Take one and cost it landed. Read is_margin off the detail response, and send the source so the fee model matches the one that priced the listing:
curl -sS -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"price":9000,"site_name":"openlane","origin":"DE","destination":"BG","is_margin":false}' \
"$API/api/calculator/calculate" | jq .breakdown.estimated_totalCompare that landed total with the retail market, not with the lot price:
curl -sS -H "X-API-Key: $KEY" \
"$API/api/cars-bg-market?brand=BMW&model=320d&year=2019&flex=1" \
| jq '{n: .snapshot.listing_count, median: .snapshot.median_price_eur}'Check listing_count before you trust the median. Market snapshots are not enabled on a new key by default — ask for them.
5. Track one car over time
Price movements for a lot you are watching:
curl -sS -H "X-API-Key: $KEY" \
"$API/api/auction/openlane/11409652/price-history" \
| jq '.history[] | {observed_at, current_price, changed_fields}'And its life before this listing, by VIN — this is what shows a car that has been through auction more than once:
curl -sS -H "X-API-Key: $KEY" \
"$API/api/vin/WBA8E9G50GNU12345/history" \
| jq '.auctions[] | {site_name, last_seen_at, mileage, public_price_eur, archived}'VIN history is not enabled on a new key by default — ask for it. event_type on price history is initial, baseline or change — there is no price_change value.
6. Mirror the whole feed, cheaply
To keep a local copy in sync rather than to answer a query:
# Unfiltered = no depth limit. Walk it at the maximum page size.
curl -sS -H "X-API-Key: $KEY" -H "Accept-Encoding: gzip" --compressed \
"$API/api/search?limit=100&offset=0&include_total=false"Four things make this an order of magnitude cheaper:
- 1
--compressed— roughly an eighth of the bytes. - 2
include_total=false— the count is the expensive half of a search. - 3Store each page's
ETagand sendIf-None-Matchon the re-walk; unchanged pages answer304with no body. - 4Do not expand every card. Hydrate detail only for rows whose
last_changed_atmoved, or that you actually display.
Search results are always cycle-priced, never live-priced. That is what makes them fast enough to page — see what stays on cycle prices.