---
title: "Recipes"
description: "Six end-to-end flows that cover most of what the API is for. Each is a complete sequence, not a fragment."
canonical: "https://thecarapi.com/docs/recipes"
contract_version: "2026-08-19"
api_base: "https://api.thecarapi.com"
source: "https://thecarapi.com/docs/recipes.md"
---

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

```bash
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.

```bash
# 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:

```bash
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

```bash
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`, or `offset`+`limit`. Mixing them is a `400`. Loop to `total_pages`; a filtered search's `total` and 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=false` and skip it entirely. If you want only the number, send `count_only=true` and get no rows.
- **Persist `site_name` + `auction_id_str`**, never `auction_id` on its own — japanauction ids exceed 2^53.

```bash
# "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 .total
```

## 3. Open one car: detail, live bid, photos

One request gets specification, condition and gallery together:

```bash
curl -sS -D headers.txt -H "X-API-Key: $KEY" --compressed \
  "$API/api/auction/openlane/11409652" -o car.json
```

Then branch on three things, in this order:

```text
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:

```bash
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:

```bash
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:

```bash
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_total
```

Compare that landed total with the retail market, not with the lot price:

```bash
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:

```bash
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:

```bash
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:

```bash
# 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.
3. Store each page's `ETag` and send `If-None-Match` on the re-walk; unchanged pages answer `304` with no body.
4. Do **not** expand every card. Hydrate detail only for rows whose `last_changed_at` moved, 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](https://thecarapi.com/docs/live-prices).
