Car Image API

Klienci · Serwisy ogłoszeniowe i katalogi zakupowe samochodów

Jak dodać studyjne zdjęcie samochodu do każdego ogłoszenia w serwisie

Jak serwisy ogłoszeniowe i katalogi samochodów generują jedno główne zdjęcie na rocznik modelowy w skali całego katalogu: identyfikatory pojazdów, kadrowanie, tempo zapytań, cache i kod.

Problem: ogłoszenie potrzebuje zdjęcia, zanim sprzedawca je prześle

O sukcesie serwisu ogłoszeniowego czy katalogu samochodów decyduje siatka ofert. Każda karta potrzebuje zdjęcia od chwili utworzenia ogłoszenia, a własne zdjęcie sprzedającego pojawia się później – o ile w ogóle – zrobione pod przypadkowym kątem, na przypadkowym podjeździe. Zdjęcia stockowe mają odwrotny problem: licencjonowane zdjęcie prasowe istnieje dla popularnych samochodów z popularnych roczników, pod kątem i na tle wybranym przez producenta, i dla niczego więcej. Katalog obejmujący lata od 1990 do bieżącego rocznika ma dziesiątki tysięcy luk.

Zespoły budujące takie serwisy chcą czegoś nudnego w najlepszym sensie: jednego studyjnego obrazu na rocznik modelu, z tym samym kątem i tym samym lakierem dla wszystkich, przezroczystego, żeby pasował do dowolnego tła karty, i na tyle lekkiego, by serwować go dziesięć tysięcy razy dziennie.

Jak wygląda integracja z katalogiem

Wszystkie katalogi budowane dziś na tym API mają ten sam schemat, niezależnie od tego, czy to serwis ogłoszeniowy zasilający swoje ogłoszenia, aplikacja zakupowa renderująca karty porównawcze, czy aplikacja mobilna pokazująca jeden główny obraz każdego pojazdu:

  • Najpierw masowe uzupełnienie, potem pojedyncze zlecenia. Tysiące renderów w kilka godzin, by objąć cały katalog, a potem kilka dziennie, w miarę pojawiania się nowych roczników i zapytań.
  • Jedno ujęcie, jeden lakier lub krótka paleta. Większość wybiera front-3-4 lub side w kolorze srebrnym lub białym. Katalog sklepowy, w którym użytkownik wybiera kolor, renderuje kilka gotowych kolorów na pojazd; każdy to jeden obraz pojazdu więcej, a nie nowy proces.
  • WebP w stałym polu. Szerokość od 512 do 1024 pikseli, dostarczane dokładnie w wymiarach, których używa karta, więc front end nigdy nie przycina obrazu ani nie dodaje pasów.
  • Przechowywanie według id pojazdu. Rendery trafiają do własnego magazynu obiektów lub CDN zespołu albo strony wczytują podpisane URL-e; tak czy inaczej kluczem jest stabilny identyfikator veh_… z katalogu, a nie ciąg marka-model-rok.

Dalsza część tej strony opisuje ten proces krok po kroku, wraz z kodem. Zakłada ona klucz API z zakresem images:read i plan dopasowany do wielkości katalogu (zob. FAQ).

Krok 1: przeglądanie katalogu według id, nigdy według pisowni

Najczęstszą przyczyną błędów przy budowie katalogu nie jest renderowanie, tylko pisownia. Katalog, który generuje nazwy marek i modeli z innej bazy danych, może zużyć większość żądań na 404 vehicle_not_found: bezpłatnie, ale każde to zmarnowane zapytanie, a w siatce zostają dziury. Rozwiązanie: niech katalog sam powie, co istnieje. GET /api/v1/vehicles jest bezpłatne, nie wymaga klucza, a każdy zwrócony model ma stały identyfikator pojazdu (veh_ plus 13 znaków), który nie zmienia się między wdrożeniami ani wydaniami katalogu. Wystarczy zapisać ten identyfikator przy ogłoszeniu i renderować na jego podstawie.

Przeglądanie jednego rocznika modelu
# every make with a 2024 model year (free, no key)
curl -s "https://carimage.dev/api/v1/vehicles?year=2024"

# every 2024 model of one make, each with the id you will store
curl -s "https://carimage.dev/api/v1/vehicles?year=2024&makeId=584"
200 OK (skrócone)
{
  "data": {
    "year": 2024,
    "make_id": 584,
    "models": [
      {
        "name": "718",
        "slug": "718",
        "vehicle_type": "Passenger Car",
        "id": "veh_b4bf090fyb53k"
      },
      {
        "name": "911",
        "slug": "911",
        "vehicle_type": "Passenger Car",
        "id": "veh_78qtwrgh37bkr"
      }
    ]
  }
}

?make=porsche&model=911 zwraca wszystkie roczniki jednego modelu z osobnym id dla każdego rocznika, a ?q= przeszukuje dowolny tekst, gdy sprzedawca wpisuje nazwę. Ten sam katalog jest publikowany jako otwarty pakiet @meterapp/vehicle-db, więc listy rozwijane i walidacja mogą działać offline na identycznej liście. Tak czy inaczej, pod koniec tego kroku mają Państwo listę id i na dalszych etapach nic już nigdy nie musi przepisywać nazwy samochodu.

Ogłoszenia, które już nazywają swoje samochody zgodnie z pisownią innej bazy danych, mapuje się jednym wywołaniem: POST /api/v1/vehicles/check odczytuje do 2000 z nich tak, jak zrobiłby to render, i dla każdego zwraca identyfikator pojazdu, najbliższe rzeczywiste pojazdy, gdy katalog nie zawiera takiego samochodu, albo brak dopasowania, który należy pominąć we wszystkich widokach i kolorach. car-image check listings.csv --out checked.csv zapisuje identyfikatory z powrotem do pliku. Jest to bezpłatne i zastępuje odkrywanie luk po jednym 404 naraz.

Krok 2: jeden format żądania dla każdego ogłoszenia

Wygląd karty ustala się raz i koduje w query stringu. Wszystko po vehicle= poniżej to decyzja o kadrowaniu podejmowana jednorazowo dla całego katalogu:

Jeden obraz do ogłoszenia
curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  "https://carimage.dev/api/v1/images/car?vehicle=veh_78qtwrgh37bkr&view=front-3-4&color=silver&w=800&h=600&fit=contain&trim=1&padding=8&format=webp" \
  -o veh_78qtwrgh37bkr.webp
  • w=800&h=600&fit=contain zwraca obraz o wymiarach dokładnie 800×600, z widocznym całym samochodem i przezroczystym marginesem wokół, dzięki czemu każda karta ma to samo płótno. fit=cover wypełnia natomiast cały obszar i przycina obraz od środka.
  • trim=1&padding=8 przycina obraz do konturu samochodu przed dopasowaniem, z marginesem równym 8% jego dłuższego boku, dzięki czemu Suburban i Miata wypełniają ramkę tak samo, zamiast unosić się w kadrze źródłowym.
  • color=silver to wartość domyślna i najbezpieczniejszy neutralny kolor; każdy z 15 predefiniowanych kolorów lub dowolny kod hex liczy się tak samo, jako jeden obraz pojazdu.
  • format=webp to najmniejsze kodowanie z bezstratnym kanałem alfa. Gdy przeglądarka ładuje URL bezpośrednio (podpisane adresy URL, krok 4), format=auto negocjuje WebP lub PNG osobno dla każdego klienta.

Odpowiedź zawiera nagłówki ETag, X-Image-Source (cache lub generated) oraz X-Vehicle-Id, czyli pojazd z katalogu, który został zwrócony. ETag należy przechowywać razem z plikiem: późniejsze żądanie z If-None-Match zwraca 304 i nic nie kosztuje – dzięki temu nocna ponowna synchronizacja pozostaje bezpłatna.

Krok 3: uzupełnianie katalogu w tempie mieszczącym się w limitach

Masowe uzupełnianie danych kształtują trzy limity, a każdy z nich sygnalizuje się w nagłówkach, zamiast zawodzić po cichu:

  • Żądania na minutę. 120 na klucz w planach Free i Pro, 600 w Business, 1200 w Enterprise, a do tego limit na konto obejmujący wszystkie klucze. Każda uwierzytelniona odpowiedź zawiera nagłówek RateLimit-Remaining; odpowiedź 429 zawiera Retry-After. Od czterech do ośmiu równoległych żądań w zupełności wystarczy.
  • Nowe widoki pojazdów. Pierwsze żądanie widoku pojazdu, którego jeszcze nie przygotowaliśmy, trwa chwilę, ponieważ przygotowujemy go tak, by można go było wyświetlić w dowolnym kolorze; każde kolejne żądanie, w dowolnym kolorze, jest obsługiwane z pamięci podręcznej. Przygotowywanie nowych widoków korzysta z udziału konta w dziennym budżecie renderowania (2% w planie Free, 10% w Pro, 25% w Business, 50% w Enterprise). Po jego wyczerpaniu nowy render zwraca 429 z code: account_generation_cap i reset_at (północ UTC), nic nie jest naliczane, a obrazy z pamięci podręcznej są nadal serwowane. Duży import zaległych danych rozkłada się na kilka wieczorów, a nie jeden.
  • Obrazy pojazdów miesięcznie. Free obejmuje 100, Pro 2500, Business 10 000; Enterprise jest negocjowany. Po wyczerpaniu limitu żądanie obrazu, który nie został jeszcze policzony w tym miesiącu, zwraca 402 (plan_image_limit) i niczego nie nalicza; obraz już policzony w tym miesiącu jest zawsze serwowany, więc działająca siatka ogłoszeń nigdy się nie psuje.

Błędy renderowania (502) automatycznie zwracają naliczony obraz pojazdu, równoczesne żądania tego samego nowego wariantu współdzielą jeden render, a zwykłe GET można bezpiecznie ponawiać. SDK samo ponawia 429 i 503, respektując Retry-After, i nigdy nie ponawia 402.

Wznawialne uzupełnianie danych (backfill) z TypeScript SDK
import { CarImageClient, CarImageError } from "@meterapp/car-image-sdk";

const client = new CarImageClient({ apiKey: process.env.CAR_IMAGE_API_KEY });

// One make, one model year: the ids are what you store and what you render by.
const { data } = await client.vehicles({ year: 2024, makeId: 584 });
const queue = data.models.flatMap((model) => (model.id ? [model.id] : []));

// Four in flight stays well inside 120 requests a minute, and a cold render
// that takes a few seconds never holds up the rest of the batch.
await Promise.all(
  Array.from({ length: 4 }, async () => {
    for (let id = queue.shift(); id; id = queue.shift()) {
      try {
        const image = await client.getImage({
          vehicle: id, view: "front-3-4", color: "silver",
          width: 800, height: 600, fit: "contain", trim: true, padding: 8, format: "webp",
        });
        await store(id, image.bytes, image.etag); // your object store or CDN, keyed by vehicle id
      } catch (error) {
        // 429 and 503 were already retried with Retry-After; a 404 is a vehicle to request, not a failure.
        if (error instanceof CarImageError && error.status === 404) continue;
        throw error;
      }
    }
  })
);

Krok 4: udostępnianie z własnego CDN lub przez podpisane adresy URL

Są dwa dobre sposoby, by pokazać obraz kupującemu, a wdrożenia katalogowe korzystają z obu:

  • Przechowywanie plików. Każdy render należy zapisać we własnym magazynie obiektów lub CDN pod identyfikatorem pojazdu i serwować jak każdy inny zasób. To właściwe rozwiązanie dla siatki wyświetlanej miliony razy: brak wywołań do nas przy każdym załadowaniu, a licencja w planie płatnym obejmuje kopie przechowywane na potrzeby Państwa produktu.
  • Generowanie podpisanych URL-i. POST /api/v1/image-urls przyjmuje do pięćdziesięciu obrazów na wywołanie, liczy obraz każdego URL-a jako obraz pojazdu w danym miesiącu w chwili wygenerowania i zwraca URL-e bez klucza, które przeglądarka wczytuje bezpośrednio. ttl_seconds można ustawić maksymalnie na siedem dni; dla stron ogłoszeń istniejących dłużej należy dodać renew: true (odnowienie liczy obraz raz w miesiącu, w którym nastąpi, i nic więcej, jeśli obraz został już policzony w tym miesiącu). Po ograniczeniu klucza w panelu do domen (origin) witryny URL skopiowany gdzie indziej zwraca 403.

Przy generowaniu partiami należy wysyłać Idempotency-Key dla każdej strony ogłoszeń: ponowione wywołanie z tym samym kluczem i treścią odtwarza pierwszą odpowiedź i niczego ponownie nie nalicza.

listings.json — po pięćdziesiąt naraz, kadrowane jak w kroku 2
{
  "images": [
    {
      "vehicle": "veh_78qtwrgh37bkr",
      "view": "front-3-4",
      "color": "silver",
      "width": 800,
      "height": 600,
      "fit": "contain",
      "trim": true,
      "padding": 8,
      "format": "auto"
    },
    {
      "vehicle": "veh_b4bf090fyb53k",
      "view": "front-3-4",
      "color": "silver",
      "width": 800,
      "height": 600,
      "fit": "contain",
      "trim": true,
      "padding": 8,
      "format": "auto"
    }
  ]
}

Gdy pojazdu nie ma w katalogu

Najpierw wyszukiwanie: GET /api/v1/vehicles?q= znajduje model po nazwie, prefiksie lub dopasowaniu przybliżonym, a POST /api/v1/images/resolve zamienia dowolną frazę tekstową na dokładne parametry wraz z poziomem pewności, na którego podstawie można działać. To, czego nadal brakuje, należy zgłosić na tablicy zgłoszeń: POST /api/v1/requests tworzy zgłoszenie, a jeśli istnieje już otwarte zgłoszenie dla tej samej marki, modelu i roku, oddaje na nie głos zamiast tworzyć duplikat. Sam katalog jest open source, a nowe pozycje trafiają do wszystkich klientów.

CLI, SDK i MCP

Cały proces działa z poziomu CLI bez ani jednej linijki kodu — w ten sposób zasilono niejeden katalog korzystający z API:

npx @meterapp/car-image
# One file per vehicle id. Re-running skips what exists, so a stopped backfill resumes.
while read -r id; do
  [ -f "cdn/$id.webp" ] || npx @meterapp/car-image get --vehicle "$id" \
      --view front-3-4 --color silver --width 800 --height 600 --fit contain \
      --trim --padding 8 --format webp --out "cdn/$id.webp"
done < vehicle-ids.txt

# Or fifty signed URLs in one call, for a page of listings the browser loads itself.
npx @meterapp/car-image url --batch listings.json --ttl 604800 --renew --json
  • SDK: client.vehicles({ year, makeId }) do wyliczania, client.getImage({ vehicle, … }) do renderowania, client.createImageUrls(…) do partii podpisanych URL-i. Wszystkie parametry opisuje dokumentacja obrazów.
  • MCP: asystent połączony z https://carimage.dev/api/mcp ma dostęp do tego samego katalogu (search_vehicles, resolve_vehicle) i tych samych renderów (get_car_image, create_car_image_urls). To właściwe narzędzie dla kuratora dodającego pojazd ręcznie, ale niewłaściwe do uzupełniania tysięcy rekordów wstecz: do tego należy użyć CLI lub SDK, a asystenta zostawić na pojedyncze zaległe przypadki. Konfiguracja jest opisana na stronie MCP.

Najczęściej zadawane pytania

Którego ujęcia użyć w ogłoszeniu?
front-3-4 to domyślny i klasyczny kąt główny, przodem w lewo. Każdy kąt lewej strony samochodu ma bliźniaczy wariant -right (front-3-4-right, side-right, rear-3-4-right), dzięki czemu karta może być zwrócona do wnętrza układu, a nie na zewnątrz. Warto wybrać jeden kąt i się go trzymać: spójność między ogłoszeniami to większość wartości.
Ile kosztuje katalog z 20 000 ogłoszeniami?
20 000 obrazów pojazdów miesięcznie, jeśli każde ogłoszenie pokazuje jeden pojazd w jednym lakierze i ujęciu, a każdy obraz jest dostarczany dowolną liczbę razy w dowolnym rozmiarze; ogłoszenia z tym samym pojazdem, lakierem i ujęciem to jeden obraz, a ponowne sprawdzanie z If-None-Match jest bezpłatne. Pro obejmuje 2500 obrazów pojazdów miesięcznie, a Business 10 000, więc katalog tej wielkości to Enterprise albo jednorazowy Image Pack, który zostaje na własność i można go na stałe hostować samodzielnie. Uzupełnianie wsteczne ponad limit zwraca 402 i nic nie jest liczone.
Czy możemy przechowywać pliki we własnym CDN?
Tak. Płatny plan udziela licencji na używanie renderów w Państwa produkcie, łącznie z kopiami na własnych serwerach lub w CDN, bez obowiązku podawania źródła, dopóki plan jest aktywny; w ciągu 30 dni od jego zakończenia przechowywane kopie są usuwane (obrazy z Image Pack można zachować na zawsze). Licencja nie pozwala natomiast oferować ich jako samodzielnej biblioteki, feedu ani zbioru danych. To aktywny plan zapewnia rendery kolejnego rocznika modelu.
Co się dzieje, gdy w katalogu pojawia się nowy rocznik modelu?
Katalog @meterapp/vehicle-db jest aktualizowany w miarę publikowania danych przez źródła, a istniejące id nigdy się nie zmieniają. Wystarczy pobrać listę dla nowego rocznika, wyrenderować id, których jeszcze nie ma, a resztę zostawić bez zmian. Odpowiedzi zawierają ETag; powtórne żądanie z If-None-Match zwraca 304 i nic nie kosztuje.
Dlaczego niektóre nasze nazwy marek i modeli zwracają 404?
Ponieważ zapisała je inna baza danych. Katalog akceptuje nazwy, slugi oraz wiele wariantów oznaczeń i typów nadwozia, ale ciąg znaków, którego nigdy nie widział, daje błąd 404 vehicle_not_found (bezpłatny, ale to zmarnowane zapytanie). Zamiast tłumaczyć własne nazwy, należy pobrać listę katalogu i zapisać jego identyfikatory, a pozostałe przypadki wyszukać lub dopasować.