Car Image API

مشتریان · سایت‌های آگهی و کاتالوگ‌های خرید خودرو

چگونه روی هر آگهی بازار، یک تصویر استودیویی خودرو قرار دهیم

سایت‌های آگهی و کاتالوگ‌های خرید خودرو چگونه برای هر سال مدل، یک تصویر اصلی در مقیاس کاتالوگ رندر می‌کنند: شناسه‌های خودرو، کادربندی، تنظیم سرعت درخواست‌ها، کش و کد.

مشکل: یک آگهی پیش از آنکه فروشنده تصویری بارگذاری کند، به تصویر نیاز دارد

موفقیت یا شکست یک سایت آگهی یا کاتالوگ خرید خودرو به شبکهٔ آگهی‌هایش بستگی دارد. هر کارت از همان لحظه‌ای که آگهی ایجاد می‌شود به تصویر نیاز دارد، و عکس خود فروشنده دیرتر می‌رسد، اگر اصلاً برسد، آن هم با زاویه‌ای تصادفی و در جلوی خانه‌ای تصادفی. عکس‌های آماده مشکلی برعکس دارند: عکس رسمی دارای مجوز فقط برای خودروهای پرطرفدار در سال‌های پرطرفدار وجود دارد، با همان زاویه و پس‌زمینه‌ای که سازنده انتخاب کرده، و برای هیچ خودروی دیگری وجود ندارد. کاتالوگی که از 1990 تا سال مدل جاری را پوشش دهد، ده‌ها هزار جای خالی دارد.

آنچه تیم‌های سازنده این سایت‌ها می‌خواهند، به بهترین معنا ساده و بی‌دردسر است: یک تصویر استودیویی برای هر سال مدل، با زاویه و رنگ یکسان برای همه، شفاف تا روی هر پس‌زمینه‌ای که کارت دارد بنشیند، و آن‌قدر کم‌حجم که بتوان روزی ده هزار بار ارائه‌اش کرد.

یکپارچه‌سازی با کاتالوگ چگونه است

پروژه‌های ساخت کاتالوگی که امروز روی این API اجرا می‌شوند همه ساختار یکسانی دارند، چه یک سایت نیازمندی‌ها باشد که آگهی‌هایش را پر می‌کند، چه یک اپلیکیشن خرید خودرو که کارت‌های مقایسه‌اش را رندر می‌کند، و چه یک اپلیکیشن موبایل که برای هر خودرو یک تصویر اصلی نمایش می‌دهد:

  • ابتدا پرکردن یکجا، سپس جریانی آرام. هزاران رندر در چند ساعت برای پوشش کاتالوگ، و پس از آن چند رندر در روز با ورود سال‌های مدل جدید و درخواست‌های تازه.
  • یک زاویه، یک رنگ یا یک پالت کوتاه. بیشتر کاربران front-3-4 یا side را با رنگ نقره‌ای یا سفید انتخاب می‌کنند. کاتالوگ فروشی که به کاربر امکان انتخاب رنگ می‌دهد، برای هر خودرو چند رنگ پیش‌فرض رندر می‌کند؛ هر کدام فقط یک تصویر خودروی دیگر است، نه یک خط پردازش جدید.
  • WebP در کادری ثابت. با عرض 512 تا 1024 پیکسل و دقیقاً در همان کادری که کارت استفاده می‌کند تحویل داده می‌شود، تا فرانت‌اند هرگز تصویر را برش ندهد یا حاشیهٔ خالی به آن اضافه نکند.
  • ذخیره بر اساس شناسهٔ خودرو. رندرها در فضای ذخیره‌سازی اشیا یا CDN خود تیم قرار می‌گیرند، یا صفحات URLهای امضاشده را بارگذاری می‌کنند؛ در هر حال، کلید همان شناسهٔ پایدار veh_… در کاتالوگ است، نه رشته‌ای از برند-مدل-سال.

بقیهٔ این صفحه همان خط پردازش است، گام‌به‌گام و همراه با کد. فرض بر این است که یک کلید API با دامنهٔ دسترسی images:read و پلنی متناسب با اندازهٔ کاتالوگ دارید (به پرسش‌های متداول مراجعه کنید).

مرحلهٔ ۱: کاتالوگ را با id فهرست کنید، هرگز با املای نام

رایج‌ترین خطا در ساخت کاتالوگ رندر نیست، املاست. ساختی که رشته‌های برند و مدل را از پایگاه داده دیگری تولید می‌کند ممکن است بیشتر درخواست‌هایش را صرف 404 vehicle_not_found کند: رایگان، اما هر کدام یک رفت‌وبرگشت هدررفته، و شبکه‌ای پر از جای خالی. راه‌حل این است که بگذارید خود کاتالوگ به شما بگوید چه چیزی وجود دارد. GET /api/v1/vehicles رایگان است، به کلید نیاز ندارد، و هر مدلی که برمی‌گرداند یک شناسه خودروی پایدار دارد (veh_ به‌علاوه ۱۳ نویسه) که در استقرارها یا انتشارهای کاتالوگ هرگز تغییر نمی‌کند. این شناسه را روی آگهی ذخیره کنید و بر اساس آن رندر بگیرید.

پیمایش یک سال مدل
# 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 تا ۲٬۰۰۰ مورد از آن‌ها را همان‌طور می‌خواند که یک رندر می‌خواند و برای هر کدام شناسهٔ خودرو، نزدیک‌ترین خودروهای واقعی (اگر کاتالوگ چنین خودرویی نداشته باشد)، یا یک «عدم تطابق» برمی‌گرداند تا در همهٔ نماها و رنگ‌ها از آن صرف‌نظر کنید. car-image check listings.csv --out checked.csv شناسه‌ها را دوباره در فایل می‌نویسد. این کار رایگان است و جایگزین کشف کمبودها، یکی‌یکی با هر 404، می‌شود.

گام ۲: یک ساختار درخواست برای همه آگهی‌ها

کارت را یک بار طراحی کنید و آن را در query string قرار دهید. هر چیزی که در نمونه زیر پس از 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 دقیقاً تصویری ۸۰۰×۶۰۰ تحویل می‌دهد که کل خودرو در آن دیده می‌شود و دورش حاشیه شفاف دارد، بنابراین همه کارت‌ها بوم یکسانی دارند. fit=cover در عوض کادر را پر می‌کند و از مرکز برش می‌دهد.
  • trim=1&padding=8 پیش از جای‌گذاری، تصویر را به خط پیرامونی خودرو برش می‌دهد و ۸٪ از ضلع بلندتر آن را حاشیه در نظر می‌گیرد، تا یک Suburban و یک Miata کادر را به یک شکل پر کنند و در قاب اصلی شناور نمانند.
  • color=silver پیش‌فرض و امن‌ترین رنگ خنثی است؛ هر یک از ۱۵ رنگ ازپیش‌تعریف‌شده یا هر کد hex به همان اندازه شمرده می‌شود: یک تصویر خودرو.
  • format=webp کوچک‌ترین کدگذاری با کانال آلفای بدون اتلاف است. وقتی مرورگر URL را مستقیماً بارگذاری می‌کند (URLهای امضاشده، گام ۴)، format=auto برای هر کلاینت WebP یا PNG را انتخاب می‌کند.

پاسخ شامل ETag، X-Image-Source (cache یا generated) و X-Vehicle-Id است که خودروی ارائه‌شده از کاتالوگ را مشخص می‌کند. ETag را همراه فایل نگه دارید: درخواست بعدی با If-None-Match پاسخ 304 می‌گیرد و هزینه‌ای ندارد. به همین دلیل همگام‌سازی مجدد شبانه رایگان می‌ماند.

مرحلهٔ ۳: تکمیل داده‌های قبلی را در چارچوب محدودیت‌ها زمان‌بندی کنید

سه محدودیت روند پرکردن اولیهٔ کاتالوگ را شکل می‌دهند و هر کدام به‌جای خطای بی‌صدا، خود را در هدرها اعلام می‌کند:

  • درخواست در دقیقه. ۱۲۰ برای هر کلید در پلن‌های Free و Pro، ۶۰۰ در Business و ۱٬۲۰۰ در Enterprise، به‌علاوهٔ یک محدودیت در سطح حساب برای همهٔ کلیدها. هر پاسخ احرازهویت‌شده شامل RateLimit-Remaining است؛ پاسخ 429 شامل Retry-After است. چهار تا هشت درخواست هم‌زمان کاملاً کافی است.
  • نماهای جدید خودرو. اولین درخواست برای نمایی از خودرو که هنوز آماده نکرده‌ایم کمی زمان می‌برد، چون آن را طوری آماده می‌کنیم که در هر رنگی قابل نمایش باشد؛ همه درخواست‌های بعدی، در هر رنگی، از کش ارائه می‌شوند. آماده‌سازی نماهای جدید از سهم حساب از بودجه رندر روزانه مصرف می‌کند (۲% در پلن Free، ۱۰% در Pro، ۲۵% در Business و ۵۰% در Enterprise). پس از عبور از این سهم، رندر جدید با 429 همراه با code: account_generation_cap و reset_at (نیمه‌شب UTC) پاسخ می‌دهد، هزینه‌ای کسر نمی‌شود و تصاویر کش‌شده همچنان ارائه می‌شوند. تکمیل یک حجم بزرگ از داده‌های قبلی چند شب طول می‌کشد، نه یک شب.
  • تصاویر خودرو در ماه. Free شامل ۱۰۰، Pro شامل ۲٬۵۰۰ و Business شامل ۱۰٬۰۰۰ تصویر است؛ 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;
      }
    }
  })
);

گام ۴: آن را از CDN خود یا از طریق URL امضاشده ارائه دهید

دو روش خوب برای نمایش تصویر به خریدار وجود دارد و پیاده‌سازی‌های کاتالوگ از هر دو استفاده می‌کنند:

  • بایت‌ها را ذخیره کنید. هر رندر را با شناسه خودروی آن در فضای ذخیره‌سازی اشیا یا CDN خودتان بنویسید و مانند هر دارایی دیگری ارائه دهید. این پاسخ درست برای شبکه‌ای است که میلیون‌ها بار نمایش داده می‌شود: در هر بارگذاری هیچ فراخوانی به ما لازم نیست و مجوز طرح پولی، نسخه‌هایی را که برای ارائه محصولتان نگه می‌دارید پوشش می‌دهد.
  • URLهای امضاشده بسازید. POST /api/v1/image-urls در هر فراخوانی تا پنجاه تصویر می‌پذیرد، تصویر هر URL را هنگام ساخت به‌عنوان یکی از تصاویر خودروی ماه می‌شمارد و URLهایی بدون کلید برمی‌گرداند که مرورگر مستقیماً بارگذاری می‌کند. ttl_seconds را تا هفت روز تنظیم کنید؛ برای صفحات آگهی که بیش از این مدت باقی می‌مانند، renew: true را اضافه کنید (هر تمدید، تصویر را یک بار در ماهی که در آن رخ می‌دهد می‌شمارد و اگر آن تصویر در آن ماه قبلاً شمرده شده باشد، چیزی اضافه نمی‌شود). در داشبورد، کلید را به originهای سایت خود محدود کنید تا URLی که جای دیگری کپی شود با 403 پاسخ دهد.

هنگام صدور دسته‌ای، برای هر صفحه از آگهی‌ها یک Idempotency-Key بفرستید: فراخوانی تکراری با همان کلید و بدنه، پاسخ اول را دوباره برمی‌گرداند و چیزی را دوباره حساب نمی‌کند.

listings.json — هر بار پنجاه عدد، با کادربندی مانند مرحله ۲
{
  "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)، بنابراین کارت می‌تواند رو به داخل صفحه باشد نه رو به بیرون. یکی را انتخاب کنید و ثابت نگه دارید: یکنواختی در میان آگهی‌ها بخش عمدهٔ ارزش آن است.
کاتالوگی با ۲۰٬۰۰۰ آگهی چقدر هزینه دارد؟
اگر هر آگهی یک خودرو را در یک رنگ و یک نما نشان دهد، ۲۰٬۰۰۰ تصویر خودرو در ماه، که هر کدام به هر تعداد دفعه و در هر اندازه‌ای تحویل داده می‌شود؛ آگهی‌هایی که خودرو، رنگ و نمای یکسانی دارند یک تصویر حساب می‌شوند و بررسی مجدد با If-None-Match رایگان است. طرح Pro شامل ۲٬۵۰۰ و طرح Business شامل ۱۰٬۰۰۰ تصویر خودرو در ماه است، بنابراین کاتالوگی در این اندازه به طرح Enterprise نیاز دارد، یا یک Image Pack یک‌باره که برای همیشه نگه می‌دارید و خودتان میزبانی می‌کنید. بارگذاری انبوهی که از سهمیه فراتر برود، پاسخ 402 می‌گیرد و هیچ چیزی شمرده نمی‌شود.
آیا می‌توانیم فایل‌ها را روی CDN خودمان نگه داریم؟
بله. پلن پولی تا زمانی که فعال است، مجوز استفاده از رندرها را درون محصول شما می‌دهد، از جمله نسخه‌هایی روی سرورها یا CDN خودتان، بدون نیاز به ذکر منبع؛ ظرف ۳۰ روز پس از پایان پلن، نسخه‌های ذخیره‌شده حذف می‌شوند (تصاویر Image Pack را می‌توان برای همیشه نگه داشت). آنچه مجوز اجازه نمی‌دهد، ارائهٔ آن‌ها به‌صورت کتابخانه، فید یا مجموعه‌داده‌ای مستقل است. این پلن فعال است که مدل‌سال بعدی را رندر می‌کند.
وقتی سال‌مدل جدیدی به کاتالوگ اضافه شود چه اتفاقی می‌افتد؟
کاتالوگ @meterapp/vehicle-db هم‌زمان با انتشار داده‌های منابع به‌روز می‌شود و شناسه‌های موجود هرگز تغییر نمی‌کنند. خودروهای سال جدید را فهرست کنید، شناسه‌هایی را که هنوز ندارید رندر کنید و به بقیه دست نزنید. درخواست‌ها ETag دارند؛ درخواست تکراری با If-None-Match پاسخ 304 می‌گیرد و هزینه‌ای ندارد.
چرا برخی از رشته‌های برند و مدل ما با خطای 404 برمی‌گردند؟
چون در پایگاه داده دیگری نوشته شده بودند. کاتالوگ نام‌ها، slugها و بسیاری از گونه‌های نشان و نوع بدنه را می‌پذیرد، اما رشته‌ای که هرگز ندیده باشد به 404 vehicle_not_found می‌انجامد (رایگان، اما یک رفت‌وبرگشت هدررفته). به‌جای ترجمه رشته‌های خودتان، کاتالوگ را فهرست کنید و شناسه‌های آن را ذخیره کنید، و موارد باقی‌مانده را جستجو یا تطبیق دهید.