راهنمای پیادهسازی که بر پایهٔ رفتار مستندشدهٔ API نوشته شده است؛ نام هیچ مشتریای در آن نیامده است. منتشرشده در .
مشکل: شما یک کلاس خودرو اجاره میدهید، نه یک خودروی مشخص
مشتری اجارهٔ خودرو یک کلاس رزرو میکند، مثلاً کامپکت، SUV متوسط یا مینیون، و با هر خودرویی از آن کلاس که در پارکینگ موجود باشد میرود. صفحهٔ رزرو همچنان برای هر کلاس به یک تصویر نیاز دارد و رسم بر این است که یک مدل نمونه با عبارت «یا مشابه» نمایش داده شود.
این تصاویر معمولاً از هر جایی میآیند که پیدا شدهاند: یک عکس مطبوعاتی اینجا، یک عکس نمایندگی آنجا، زاویههای متفاوت، پسزمینههای متفاوت، خودروهایی که کارتهایشان را به شکل متفاوتی پر میکنند. شبکهای که اینطور سرهم شده باشد، سرهمشده به نظر میرسد. آنچه صفحه ناوگان میخواهد درست برعکس است: هر کلاس به یک شکل ترسیم شود تا تنها چیزی که از کارتی به کارت دیگر تغییر میکند، خود خودرو باشد.
یکپارچهسازی چگونه است
- یک مدل نماینده برای هر کلاس، که بهصورت شناسهٔ خودرو در کاتالوگ نگه داشته میشود و عبارت «یا مشابه» در برچسب میآید، نه هرگز در تصویر.
- یک ساختار درخواست واحد برای همهٔ کارتها: همان نما، کادر، نحوهٔ جاگیری، برش، فاصلهٔ داخلی، رنگ، پسزمینه و فرمت.
- حداکثر دو تصویر برای هر کلاس: کارت در شبکهٔ فهرست و تصویر اصلی در صفحهٔ کلاس.
- ذخیره در کش، چون شبکهٔ خودروها برای همهٔ بازدیدکنندگان یکسان است: فایلها روی CDN شما، یا URLهای امضاشدهای که یک بار ساخته و دوباره استفاده میشوند.
مرحلهٔ ۱: برای هر رده، یک مدل نماینده را با شناسه انتخاب کنید
مدلی را که هر کلاس با آن عرضه میشود انتخاب کنید و سال مدل فعلی آن را پیدا کنید. GET /api/v1/vehicles?make=&model= رایگان و عمومی است و همه سالهای یک مدل را فهرست میکند، هر کدام با شناسه پایدار خودرو که آن را ذخیره خواهید کرد؛ POST /api/v1/images/resolve همین کار را از روی عبارتی مانند «2026 toyota rav4» انجام میدهد.
# one model's years, each with its vehicle id (free, public)
curl -s "https://carimage.dev/api/v1/vehicles?make=toyota&model=rav4" | jq -c '.data.vehicles[] | select(.year == 2026)'
# {"year":2026,"id":"veh_e5w75c93f6cvk"}// One representative model per class: its current model year, by catalog id.
// "or similar" lives in the label; the picture never promises the car on the lot.
export const CLASS_IMAGES = {
compact: { vehicle: "veh_fy7r2yvwt4p1v", label: "Toyota Corolla or similar" }, // 2026 Toyota Corolla
"midsize-suv": { vehicle: "veh_e5w75c93f6cvk", label: "Toyota RAV4 or similar" }, // 2026 Toyota RAV4
"fullsize-suv": { vehicle: "veh_9mzh9ejqjye43", label: "Chevrolet Tahoe or similar" }, // 2026 Chevrolet Tahoe
minivan: { vehicle: "veh_9jck7rrybf35p", label: "Chrysler Pacifica or similar" }, // 2026 Chrysler Pacifica
};شناسهها هرگز تغییر نمیکنند، بنابراین این نگاشت تنها جایی است که تغییر ناوگان به آن مربوط میشود: وقتی کلاس میاناندازه به مدل دیگری یا به مدلسال بعدی منتقل میشود، یک شناسه را جایگزین کنید تا همهٔ کارتها، تصاویر اصلی و ایمیلهایی که از این نگاشت میخوانند بهروز شوند.
مرحلهٔ ۲: یک ساختار درخواست برای همهٔ کلاسها
۹ نمای دوربین برای همهٔ خودروهای کاتالوگ بهطور یکسان کادربندی شدهاند، و همین باعث میشود یک شبکه همتراز باشد. بقیهٔ کارت چند پارامتر است که یک بار تعیین میشوند:
view=sideبرای کارتها: همهٔ خودروها رو به یک سمت و روی یک خط پایه قرار میگیرند. هر زاویهٔ سمت چپ یک همتای-rightدارد (side-right)، بنابراین کارتها میتوانند رو به داخل صفحه باشند. صفحهٔ کلاس ازfront-3-4، یعنی زاویهٔ تصویر اصلی، استفاده میکند.w=640&h=320&fit=contain: دقیقاً 640×320 برای همهٔ کلاسها، با کل خودرو در کادر. هدرهایX-Image-WidthوX-Image-Heightدر پاسخ، ابعاد بوم را تأیید میکنند.trim=1&padding=6: پیش از جایدادن خودرو، تصویر را تا لبههای خودرو برش میدهد و ۶٪ از ضلع بلندتر آن را بهعنوان حاشیه در نظر میگیرد تا یک Corolla و یک Tahoe کارتهایشان را به یک شکل پر کنند، نه اینکه در کادر مربعی تصویر منبع شناور بمانند.background=f4f4f4کارت را روی همان رنگ خاکستری جدول یکدست میکند. برای حفظ پسزمینهٔ شفاف و نمایان شدن رنگ کارت، آن را حذف کنید.format=webpبرای فایلهای ذخیرهشده؛format=autoروی URLهای امضاشده، که به هر مرورگر WebP یا PNG ارائه میدهد.
# the class card: the same box, view, crop, paint and backdrop for every class
curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
"https://carimage.dev/api/v1/images/car?vehicle=veh_e5w75c93f6cvk&view=side&color=white&w=640&h=320&fit=contain&trim=1&padding=6&background=f4f4f4&format=webp" \
-o midsize-suv-card.webp
# the class page's hero: the three-quarter view, the full width the API delivers
curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
"https://carimage.dev/api/v1/images/car?vehicle=veh_e5w75c93f6cvk&view=front-3-4&color=white&w=1024&format=webp" \
-o midsize-suv-hero.webpمرحلهٔ ۳: یک رنگ نامدار برای کل شبکهٔ کارتها
۱۵ رنگ آماده، رنگهای نامدار هستند: white, black, gray, silver, blue, red, green, brown, beige, tan, orange, yellow, gold, burgundy, purple. پیشفرض silver است و grey بهصورت gray خوانده میشود. برای همهٔ کلاسهای خودرو یک رنگ انتخاب کنید تا کارتها یک مجموعهٔ یکدست به نظر برسند؛ white در بالا نشان داده شده است. هر رنگ آماده نمونهرنگ خودش را دارد، نه رنگ CSS همنام (red برابر #b3151b است)، و هر رنگ، از جمله یک کد hex، رنگآمیزی دوبارهٔ یک نمای آمادهشده است، پس هر رنگ یک تصویر خودرو محسوب میشود.
مرحلهٔ ۴: فایلها یا URLها را کش کنید
شبکهٔ کلاسها برای همهٔ بازدیدکنندگان یکسان است، پس هزینهٔ هر بازدید باید تقریباً صفر باشد. دو راه برای رسیدن به این هدف وجود دارد:
- فایلها را ذخیره کنید. هر کارت و تصویر اصلی را یک بار با
GET /api/v1/images/carدریافت کنید و مانند هر دارایی دیگری از CDN خود ارائه دهید؛ طرح پولی تا زمانی که فعال است اجازه میدهد نسخههایی را روی سرورهای خودتان برای ارائه در محصولتان نگه دارید (ظرف ۳۰ روز پس از پایان طرح آنها را حذف کنید، یا یک Image Pack بخرید تا مجموعهای را برای همیشه نگه دارید).ETagهر فایل را نگه دارید: بررسی دوباره شبانه باIf-None-Matchپاسخ304میگیرد و هزینهای ندارد. برای ناوگان بالا، این یعنی ۸ تصویر خودرو در ماه که هر چند بار لازم باشد دوباره استفاده میشود. - یا URLهای امضاشده را یک بار بسازید و دوباره از آنها استفاده کنید.
POST /api/v1/image-urlsدر هر فراخوانی حداکثر ۵۰ تصویر میپذیرد و URLهایی بدون کلید برمیگرداند که تا ۷ روز معتبرند. URL با تعداد استفاده نامحدود در طول عمرش بهصورت عمومی قابل کش است، بنابراین وقتی همه بازدیدکنندگان یک URL یکسان را بارگیری میکنند، درخواستهای تکراری رایگان از CDN ارائه میشوند.renew: trueرا اضافه کنید تا URL ذخیرهشده پس از پایان عمرش هم کار کند؛ هر تمدید تصویر را یک بار در ماهی که در آن انجام میشود حساب میکند و اگر تصویر در آن ماه قبلاً حساب شده باشد چیزی اضافه نمیشود، بنابراین ناوگان بالا حداکثر ۸ تصویر خودرو در ماه باقی میماند.
کلیدی را که URLها را تولید میکند، در داشبورد به دامنه رزرو خود محدود کنید تا URLی که در سایت دیگری کپی شود، پاسخ 403 بدهد.
import { CarImageClient, type ImageParams } from "@meterapp/car-image-sdk";
import { CLASS_IMAGES } from "./fleet";
const client = new CarImageClient({ apiKey: process.env.CAR_IMAGE_API_KEY });
// Framed once, for every class.
const card = (vehicle: string): ImageParams => ({
vehicle, view: "side", color: "white", width: 640, height: 320,
fit: "contain", trim: true, padding: 6, background: "#f4f4f4", format: "auto",
});
const hero = (vehicle: string): ImageParams => ({ vehicle, view: "front-3-4", color: "white", width: 1024, format: "auto" });
// Every visitor sees the same grid: mint the URLs once and serve the stored ones.
// renew keeps each URL working past its 7-day lifetime, one credit per image
// for each further week in which it is loaded.
const classes = Object.entries(CLASS_IMAGES);
const { data } = await client.createImageUrls(
classes.flatMap(([, entry]) => [card(entry.vehicle), hero(entry.vehicle)]),
{ ttlSeconds: 604800, renew: true, idempotencyKey: "class-grid-2026-10" }
);
await saveClassImages(
classes.map(([key], index) => ({ key, card: data[2 * index].url, hero: data[2 * index + 1].url, renewsUntil: data[2 * index].renews_until }))
);مرحلهٔ ۵: تأیید رزرو
ایمیل تأیید، کلاس رزروشده را نشان میدهد و مستأجر هر زمان که فرصت کند آن را باز میکند. تصویر آن را بهصورت یک URL خودتمدیدشونده بسازید: ttl_seconds روی حداکثر ۷ روز، renew: true و renew_days تا بعد از تاریخ بازگرداندن خودرو. هفتههایی که کسی ایمیل را باز نمیکند هزینهای ندارند و پس از پایان اجاره، URL دیگر تمدید نمیشود. یک Idempotency-Key برای هر رزرو باعث میشود ارسال مجدد، همان URL اول را برگرداند بهجای ساختن URL دوم، و ذخیرهٔ URL روی رزرو امکان استفادهٔ دوباره از آن را در همهٔ ارسالهای بعدی فراهم میکند. ایمیلهای چرخهٔ عمر خود ما هم به همین شکل کار میکنند؛ سازوکار و HTML آن در داستان ایمیل آمده است.
// The booking confirmation: opened today, again at the counter, maybe never.
const { data: [classImage] } = await client.createImageUrls(card(booking.classVehicle), {
ttlSeconds: 604800,
renew: true,
// A week past the return date, and no renewals after it (365 days at most).
renewDays: Math.min(daysUntil(booking.returnDate) + 7, 365),
// A retried send replays the first URL instead of minting a second one.
idempotencyKey: `booking:${booking.id}:class`,
});
booking.classImageUrl = classImage.url; // later resends reuse itCLI، SDK و MCP
- CLI:
npx @meterapp/car-image get --vehicle veh_… --view side --width 640 --height 320 --fit contain --trim --padding 6 --out card.webpیک کارت را در فایل مینویسد؛car-image url --batch classes.json --renewدستهای از URLها را صادر میکند. - SDK:
client.vehicles({ make, model })برای شناسهها،client.getImage(…)برای فایلها،client.createImageUrls(…)برای URLها. - MCP:
resolve_vehicleوcreate_car_image_urlsبه دستیار امکان میدهند از عبارتی مانند «SUV میاناندازه، RAV4 یا مشابه» پیشنویس یک کارت کلاس را تهیه کند. راهاندازی در صفحه MCP توضیح داده شده است.
پرسشهای متداول
- آیا کارت میتواند دقیقاً همان خودرویی را که مستأجر تحویل میگیرد نشان دهد؟
- خیر، و نباید هم چنین کند. هر کلاس با عبارت «یا مشابه» اجاره داده میشود و رندر، برند، مدل و سال یک خودروی کاتالوگ را نشان میدهد، نه خودرویی در پارکینگ شما. یک مدل نماینده را با «یا مشابه» در برچسب نمایش دهید. وقتی خودروی مشخصی در پیشخوان تخصیص داده شد، اگر بخواهید بهجای آن همان مدل را نمایش دهید، VIN آن بهصورت رایگان به خودروی متناظر در کاتالوگ رمزگشایی میشود.
- چرا خودروهای داخل شبکه با مقیاس واقعی نمایش داده نمیشوند؟
- چون trim هر رندر را به اندازهٔ خودرو برش میدهد و fit=contain کارت را با آن پر میکند، بنابراین یک SUV کامپکت و یک SUV بزرگ کارتهایشان را به یک شکل پر میکنند. همین است که شبکه را مرتب نگه میدارد. اندازه را با کلمات بیان کنید: نام کلاس، تعداد صندلیها و چمدانها.
- هزینهٔ شبکهٔ کلاسها چقدر است؟
- ناوگانی با ۴ کلاس که هر کدام یک تصویر کارت و یک تصویر اصلی دارد، ۸ تصویر خودرو در ماه است که هر کدام را میتوان هر چند بار لازم بود و در هر اندازهای تحویل گرفت. اگر فایلها را تا زمانی که پلن پولی فعال است روی CDN خودتان ذخیره کنید، فقط در ماهی که دریافتشان میکنید حساب میشوند و بررسی مجدد با ETag رایگان است. اگر بهجای آن URLهای امضاشدهٔ قابلتمدید ارائه دهید، همان تصاویر در هر ماهی که صفحهها باز شوند یک بار حساب میشوند.
- شبکهٔ کارتها از کدام رنگ بدنه استفاده کند؟
- یک رنگ آماده برای همه کلاسها، تا کارتها مجموعهای یکدست به نظر برسند. ۱۵ رنگ آماده عبارتاند از white, black, gray, silver, blue, red, green, brown, beige, tan, orange, yellow, gold, burgundy, purple و رنگ پیشفرض silver است؛ هر کد hex هم با همان قیمت کار میکند. مستأجر رنگی را دریافت میکند که در پارکینگ موجود است، و کارت هرگز وعده رنگ دیگری نداده بود.
- اگر مدل یک کلاس در کاتالوگ نباشد چه؟
- برای خودرویی که در کاتالوگ وجود ندارد، پاسخ 404 همراه با پیشنهادهایی برگردانده میشود: نزدیکترین خودروهای کاتالوگ، هر کدام با شناسهٔ خود. از یکی از آنها استفاده کنید یا خودرو را در تابلوی عمومی درخواستها درخواست کنید. کاتالوگ متنباز است.