---
title: "Code examples"
description: "Minimal clients in cURL, TypeScript, Python and PHP, plus recipes for pagination, conditional requests and detail pages."
canonical: "https://thecarapi.com/docs/code-examples"
contract_version: "2026-08-19"
api_base: "https://api.thecarapi.com"
source: "https://thecarapi.com/docs/code-examples.md"
---

# 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
            return
```

_`include_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._
