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 — japanauction 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
Top offers already carry the market reference the verdict was made against:
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"}' \
"$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.