Reference
Code examples
Minimal clients in cURL, TypeScript, Python and PHP, plus recipes for pagination, conditional requests and detail pages.
There is no required SDK. Any HTTP client that can send headers and parse JSON will work. The examples below are complete — paste one, set THECARAPI_KEY, and it runs.
cURL
bash
export THECARAPI_KEY="your_key"
curl -sS --compressed \
-H "X-API-Key: $THECARAPI_KEY" \
"https://api.thecarapi.com/api/search?brand=bmw&fuel=Diesel&year_from=2018&sort=price_low&limit=24" \
| jq '.results[] | {auction_id, site_name, clean_make, clean_model, public_price_eur}'TypeScript
typescript
const BASE = "https://api.thecarapi.com";
interface SearchResponse {
success: boolean;
results: SearchCard[];
total: number | null;
limit: number;
offset: number;
max_page: number | null;
random_seed: string | null;
contract_version: string;
request_id: string;
}
interface SearchCard {
auction_id: number;
site_name: string;
clean_make: string;
clean_model: string;
registration_year: number;
mileage: number;
/** Prices are JSON numbers, never strings. */
public_price_eur: number;
thumbnail_url: string | null;
}
async function search(params: Record<string, string | number>): Promise<SearchResponse> {
const url = `${BASE}/api/search?${new URLSearchParams(
Object.entries(params).map(([k, v]) => [k, String(v)]),
)}`;
const res = await fetch(url, {
headers: {
"X-API-Key": process.env.THECARAPI_KEY!,
"Accept-Encoding": "gzip",
},
});
if (!res.ok) {
throw new Error(`${res.status} — request ${res.headers.get("X-Request-ID")}`);
}
return res.json();
}
const page = await search({ brand: "bmw", fuel: "Diesel", limit: 24 });
console.log(page.results.length, "of", page.total);Python
python
import os
import requests
BASE = "https://api.thecarapi.com"
session = requests.Session()
session.headers.update({"X-API-Key": os.environ["THECARAPI_KEY"]})
# requests negotiates gzip and decompresses transparently.
response = session.get(
f"{BASE}/api/search",
params={"brand": "bmw", "fuel": "Diesel", "year_from": 2018, "limit": 24},
timeout=30,
)
response.raise_for_status()
payload = response.json()
for car in payload["results"]:
print(car["site_name"], car["auction_id"], car["public_price_eur"])PHP
php
<?php
$client = new GuzzleHttp\Client([
'base_uri' => 'https://api.thecarapi.com',
'headers' => ['X-API-Key' => getenv('THECARAPI_KEY')],
'decode_content' => 'gzip', // Guzzle needs this told to it explicitly
'timeout' => 30,
]);
$response = $client->get('/api/search', [
'query' => ['brand' => 'bmw', 'fuel' => 'Diesel', 'limit' => 24],
]);
$payload = json_decode((string) $response->getBody(), true);
foreach ($payload['results'] as $car) {
printf("%s/%d — €%s\n", $car['site_name'], $car['auction_id'], $car['public_price_eur']);
}Recipe: page through a result set
python
def iter_results(session, **filters):
"""Yield every card, respecting the depth policy rather than guessing at it."""
offset, limit = 0, 100
while True:
r = session.get(f"{BASE}/api/search",
params={**filters, "limit": limit, "offset": offset,
"include_total": "false"},
timeout=30)
r.raise_for_status()
rows = r.json()["results"]
if not rows:
return
yield from rows
offset += limit
if offset >= 5000: # public depth cap; read it from /api/contract
returninclude_total=false skips the count, which is the expensive half of a deep search.Recipe: poll a search page without re-downloading it
javascript
let etag = null;
let cached = null;
async function poll(url) {
const res = await fetch(url, {
headers: {
"X-API-Key": process.env.THECARAPI_KEY,
...(etag ? { "If-None-Match": etag } : {}),
},
});
if (res.status === 304) return cached; // nothing changed, no body transferred
etag = res.headers.get("ETag"); // opaque — echo it back exactly
cached = await res.json();
return cached;
}Recipe: a vehicle detail page in one request
javascript
const { auction } = await getJson(`/api/auction/${site}/${id}`);
const photos = auction.vault_gallery?.images ?? [];
const stillComing = auction.vault_gallery?.pending ?? 0; // render placeholders
const price = auction.public_price_eur; // already live if it could be
const asOf = auction.live_price
? new Date(auction.live_price.fetched_at * 1000) // seconds → ms
: null;
// Only if the refresh missed the request budget, read exactly once more.
if (!auction.live_price && auction.live_price_pending) {
setTimeout(() => refetch(site, id), 2000);
}vault_gallery is the same body /api/auction-images/{site}/{id} returns, so the second request is unnecessary.