Car Image API

Клиенты · Сайты объявлений и каталоги для выбора автомобилей

Как добавить студийное изображение автомобиля к каждому объявлению на маркетплейсе

Как сайты объявлений и автомобильные каталоги создают по одному главному изображению на каждый модельный год в масштабе всего каталога: идентификаторы автомобилей, кадрирование, темп запросов, кэширование и код.

Проблема: объявлению нужно изображение ещё до того, как продавец загрузит своё

Сайт объявлений или автомобильный каталог живёт и умирает вместе со своей сеткой. Каждой карточке нужно изображение с момента появления объявления, а собственное фото продавца приходит позже, если вообще приходит, — под случайным углом на случайной подъездной дорожке. У стоковой фотографии обратная проблема: лицензированный пресс-снимок есть для популярных автомобилей популярных лет, под тем углом и на том фоне, что выбрал производитель, и больше ни для чего. В каталоге, охватывающем период с 1990 по текущий модельный год, десятки тысяч пробелов.

То, чего хотят команды, создающие такие сайты, скучно в лучшем смысле слова: одно студийное изображение на модельный год, один и тот же ракурс и один и тот же цвет для всех, прозрачный фон, чтобы картинка смотрелась на любом фоне карточки, и небольшой размер, чтобы её можно было отдавать десять тысяч раз в день.

Как выглядит интеграция с каталогом

Все каталоги, которые сегодня работают на API, устроены одинаково — будь то сайт объявлений, заполняющий свои объявления, приложение для выбора автомобиля, отображающее карточки сравнения, или мобильное приложение с одним главным изображением для каждого автомобиля:

  • Сначала массовая загрузка, потом понемногу. Тысячи рендеров за несколько часов, чтобы охватить каталог, а затем по несколько в день по мере появления новых модельных годов и запросов.
  • Один ракурс, один цвет или небольшая палитра. Чаще всего выбирают front-3-4 или side в серебристом или белом цвете. Каталог для покупателей, где пользователь может выбрать цвет, рендерит несколько предустановленных цветов для каждого автомобиля; каждый из них — это ещё одно изображение автомобиля, а не новый конвейер.
  • WebP фиксированного размера. Ширина от 512 до 1024 пикселей, изображение приходит ровно в тех размерах, которые использует карточка, поэтому фронтенду не приходится его обрезать или добавлять поля.
  • Хранение по id автомобиля. Рендеры попадают в собственное объектное хранилище или CDN команды, либо страницы загружают подписанные URL; в любом случае ключом служит стабильный id из каталога veh_…, а не строка «марка-модель-год».

Далее на этой странице — пошаговое описание этого конвейера с кодом. Предполагается, что у вас есть API-ключ с областью доступа images:read и тариф, рассчитанный на размер каталога (см. FAQ).

Шаг 1: перебирайте каталог по id, а не по написанию

Чаще всего сборка каталога ломается не на рендеринге, а на написании названий. Сборка, которая берёт строки марок и моделей из другой базы данных, может тратить большую часть запросов на 404 vehicle_not_found: это бесплатно, но каждый раз — впустую потраченный запрос и сетка с пробелами. Решение — позволить каталогу сообщить вам, что в нём есть. GET /api/v1/vehicles бесплатен, не требует ключа, и у каждой возвращаемой им модели есть стабильный id автомобиля (veh_ плюс 13 символов), который никогда не меняется между деплоями и релизами каталога. Сохраните этот id в объявлении и запрашивайте рендеры по нему.

Перебор одного модельного года
# 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 (сокращено)
{
  "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 выводит все годы одной модели с отдельным id для каждого года, а ?q= выполняет поиск по свободному тексту, когда продавец вводит название. Тот же каталог опубликован как открытый пакет @meterapp/vehicle-db, поэтому выпадающие списки и проверка могут работать офлайн по идентичному списку. В любом случае к концу этого шага у вас есть список id, и дальше ничто в системе больше не пишет название автомобиля вручную.

Объявления, в которых автомобили уже названы так, как принято в другой базе данных, сопоставляются за один вызов: POST /api/v1/vehicles/check читает до 2 000 из них так же, как это сделал бы рендер, и для каждого возвращает id автомобиля, ближайшие реальные автомобили, если такой машины в каталоге нет, или промах, который нужно пропускать во всех ракурсах и цветах. car-image check listings.csv --out checked.csv записывает id обратно в файл. Это бесплатно и избавляет от необходимости обнаруживать пробелы по одному 404 за раз.

Шаг 2: один формат запроса для всех объявлений

Определите вид карточки один раз и закодируйте его в строке запроса. Всё, что идёт после vehicle= ниже, — это решение о кадрировании, которое вы принимаете один раз для всего каталога:

Одно изображение для объявления
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 выдаёт ровно 800×600: автомобиль виден целиком, а вокруг — прозрачные поля, так что у каждой карточки одинаковый холст. fit=cover вместо этого заполняет область и обрезает изображение по центру.
  • trim=1&padding=8 перед вписыванием обрезает изображение по контуру автомобиля, оставляя поле в 8% от его длинной стороны, поэтому Suburban и Miata заполняют рамку одинаково, а не теряются в исходном кадре.
  • color=silver — значение по умолчанию и самый безопасный нейтральный вариант; любой из пресетов (15) или любой hex-код учитывается одинаково — как одно изображение автомобиля.
  • format=webp — самый компактный формат с альфа-каналом без потерь. Когда браузер загружает URL напрямую (подписанные URL, шаг 4), format=auto выбирает WebP или PNG для каждого клиента.

Ответ содержит ETag, X-Image-Source (cache или generated) и X-Vehicle-Id — автомобиль из каталога, который был отдан. Храните ETag вместе с файлом: последующий запрос с If-None-Match вернёт 304 и ничего не будет стоить — так ночная повторная синхронизация остаётся бесплатной.

Шаг 3: распределите заполнение каталога во времени в рамках лимитов

Массовую первоначальную загрузку определяют три ограничения, и каждое из них сообщает о себе в заголовках, а не срабатывает молча:

  • Запросов в минуту. 120 на ключ на тарифах Free и Pro, 600 на Business, 1 200 на Enterprise, а также общий лимит на аккаунт для всех ключей. Каждый аутентифицированный ответ содержит RateLimit-Remaining; ответ 429 содержит Retry-After. Четырёх–восьми одновременных запросов вполне достаточно.
  • Новые ракурсы автомобилей. Первый запрос ракурса автомобиля, который мы ещё не подготовили, занимает немного времени: мы готовим его, чтобы его можно было показывать в любом цвете; все последующие запросы, в любом цвете, отдаются из кэша. Подготовка новых ракурсов расходует долю аккаунта в дневном бюджете рендеринга (2% на Free, 10% на Pro, 25% на Business, 50% на Enterprise). После её исчерпания новый рендер отвечает 429 с code: account_generation_cap и reset_at (полночь UTC), ничего не списывается, а кэшированные изображения продолжают отдаваться. Крупная дозагрузка займёт несколько вечеров, а не один.
  • Изображения автомобилей в месяц. Free включает 100, Pro — 2 500, Business — 10 000; для Enterprise объём согласуется. После исчерпания лимита запрос изображения, ещё не учтённого в этом месяце, получает ответ 402 (plan_image_limit) и ничего не учитывает; уже учтённое в этом месяце изображение выдаётся всегда, поэтому работающая сетка объявлений не сломается.

При ошибках рендеринга (502) изображение автомобиля автоматически возвращается в лимит, одновременные запросы одного и того же нового варианта используют один рендер, а простой GET можно безопасно повторять. SDK сам повторяет запросы при 429 и 503, соблюдая Retry-After, и никогда не повторяет при 402.

Возобновляемая массовая загрузка с 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;
      }
    }
  })
);

Шаг 4: отдавайте изображение со своей CDN или через подписанные URL

Есть два хороших способа показать изображение покупателю, и в сборках каталогов используются оба:

  • Храните файлы у себя. Записывайте каждый рендер в собственное объектное хранилище или CDN под идентификатором автомобиля и отдавайте его как любой другой ресурс. Это правильное решение для сетки, которая отображается миллионы раз: никаких обращений к нам при каждой загрузке, а лицензия платного тарифа распространяется на копии, которые вы храните для работы своего продукта.
  • Создавайте подписанные URL. POST /api/v1/image-urls принимает до пятидесяти изображений за вызов, засчитывает изображение каждого URL как изображение автомобиля за месяц в момент создания и возвращает URL без ключа, которые браузер загружает напрямую. Задайте ttl_seconds до семи дней; добавьте renew: true для страниц объявлений, которые живут дольше (продление засчитывает изображение один раз в месяце, на который оно приходится, и ничего сверх, если оно уже было учтено в этом месяце). Ограничьте ключ источниками вашего сайта в панели управления — и URL, скопированный в другое место, вернёт 403.

При пакетном создании ссылок отправляйте Idempotency-Key для каждой страницы объявлений: повторный вызов с тем же ключом и телом воспроизводит первый ответ и ничего не учитывает повторно.

listings.json — по пятьдесят за раз, с кадрированием как на шаге 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"
    }
  ]
}

Когда автомобиля нет в каталоге

Сначала выполните поиск: GET /api/v1/vehicles?q= находит модель по названию, префиксу или нечёткому совпадению, а POST /api/v1/images/resolve превращает произвольную фразу в точные параметры с оценкой уверенности, на которую можно опираться. То, чего по-прежнему не хватает, стоит добавить на доску запросов: POST /api/v1/requests создаёт запрос, а если открытый запрос на ту же марку, модель и год уже существует, голосует за него вместо создания дубликата. Сам каталог открыт, и дополнения становятся доступны всем клиентам.

CLI, SDK и MCP

Весь конвейер запускается из CLI вообще без кода — именно так был наполнен не один каталог на этом 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 }) для перечисления, client.getImage({ vehicle, … }) для рендера, client.createImageUrls(…) для пакетов подписанных URL. Все параметры описаны в документации по изображениям.
  • MCP: ассистенту, подключённому к https://carimage.dev/api/mcp, доступны тот же каталог (search_vehicles, resolve_vehicle) и те же рендеры (get_car_image, create_car_image_urls). Это подходящий инструмент для куратора, добавляющего автомобиль вручную, и неподходящий для массового заполнения тысяч записей: для этого используйте CLI или SDK, а ассистента оставьте для отдельных оставшихся случаев. Настройка описана на странице MCP.

Часто задаваемые вопросы

Какой ракурс использовать для объявления?
front-3-4 — ракурс по умолчанию и классический главный ракурс, передом влево. У каждого ракурса левой стороны есть зеркальный -right (front-3-4-right, side-right, rear-3-4-right), чтобы карточка была обращена внутрь макета, а не наружу. Выберите один и придерживайтесь его: единообразие между объявлениями — главная ценность.
Сколько стоит каталог из 20 000 объявлений?
20 000 изображений автомобилей в месяц, если в каждом объявлении показан один автомобиль в одном цвете и ракурсе; каждое изображение доставляется сколько угодно раз в любом размере. Объявления с одинаковым автомобилем, цветом и ракурсом — это одно изображение, а повторная проверка с If-None-Match бесплатна. Pro включает 2 500 изображений автомобилей в месяц, Business — 10 000, поэтому каталогу такого размера нужен Enterprise или разовый Image Pack, который остаётся у вас навсегда и размещается на вашем хостинге. Массовая загрузка сверх лимита возвращает 402 и ничего не списывает.
Можно ли хранить файлы на собственном CDN?
Да. Платный тариф лицензирует рендеры для использования внутри вашего продукта, включая копии на ваших серверах или CDN, без указания авторства, пока тариф активен; в течение 30 дней после его окончания сохранённые копии удаляются (изображения из Image Pack можно хранить бессрочно). Лицензия не разрешает предлагать их как самостоятельную библиотеку, фид или датасет. Именно активный тариф позволяет рендерить автомобили следующего модельного года.
Что происходит, когда в каталоге появляется новый модельный год?
Каталог @meterapp/vehicle-db обновляется по мере публикации данных источниками, а существующие id никогда не меняются. Получите список моделей нового года, отрендерите id, которых у вас ещё нет, а остальные не трогайте. Запросы возвращают ETag; повторный запрос с If-None-Match получает ответ 304 и ничего не стоит.
Почему некоторые наши строки с маркой и моделью возвращают 404?
Потому что их написание взято из другой базы данных. Каталог принимает названия, slug и многие варианты шильдиков и типов кузова, но незнакомая ему строка вернёт 404 vehicle_not_found (бесплатно, но это напрасный запрос). Получите полный список каталога и храните его идентификаторы вместо того, чтобы сопоставлять собственные строки, а оставшиеся найдите через поиск или resolve.