المشكلة: يحتاج الإعلان إلى صورة قبل أن يرفع البائع صورته
يتوقف نجاح موقع الإعلانات المبوّبة أو كتالوج شراء السيارات على شبكة العرض فيه. فكل بطاقة تحتاج إلى صورة لحظة إنشاء الإعلان، بينما تصل صورة البائع لاحقًا، إن وصلت أصلًا، بزاوية عشوائية وفي ممر منزل عشوائي. أما صور المخزون الجاهزة فتعاني من المشكلة المعاكسة: توجد صورة صحفية مرخّصة للسيارات الشائعة في السنوات الشائعة، بالزاوية والخلفية التي اختارتها الشركة المصنّعة، ولا شيء غير ذلك. والكتالوج الذي يغطي الفترة من 1990 حتى سنة الطراز الحالية فيه عشرات الآلاف من الفجوات.
ما تريده الفرق التي تبني هذه المواقع مملّ بأفضل معنى للكلمة: صورة استوديو واحدة لكل سنة طراز، بالزاوية نفسها والطلاء نفسه للجميع، شفافة لتندمج مع أي خلفية للبطاقة، وصغيرة بما يكفي لتقديمها عشرة آلاف مرة يوميًا.
كيف يبدو التكامل مع الكتالوج
تتخذ جميع عمليات بناء الكتالوج العاملة على API اليوم الشكل نفسه، سواء كانت موقع إعلانات مبوّبة يملأ قوائمه، أو تطبيقًا لتسوّق السيارات يعرض بطاقات المقارنة، أو تطبيقًا للهاتف المحمول يعرض صورة رئيسية واحدة لكل مركبة:
- تعبئة أولية، ثم تدفق هادئ. آلاف الصور المُولَّدة خلال بضع ساعات لتغطية الكتالوج، ثم بضع صور يوميًا مع وصول سنوات الطرازات الجديدة والطلبات الجديدة.
- زاوية واحدة، أو لون طلاء واحد، أو مجموعة ألوان صغيرة. يختار معظم العملاء
front-3-4أوsideباللون الفضي أو الأبيض. أما كتالوج التسوق الذي يتيح للمستخدم اختيار اللون فيعرض بضعة ألوان محددة مسبقًا لكل مركبة؛ وكل لون منها صورة مركبة إضافية، لا مسار عمل جديد. - WebP بأبعاد ثابتة. بعرض من 512 إلى 1024 بكسل، تُسلَّم بالأبعاد نفسها التي تستخدمها البطاقة تمامًا، فلا تضطر الواجهة الأمامية أبدًا إلى القص أو إضافة أشرطة فارغة.
- تخزين حسب معرّف المركبة. تصل الصور إلى مخزن الكائنات أو شبكة CDN الخاصة بالفريق، أو تحمّل الصفحات عناوين URL موقّعة؛ وفي كلتا الحالتين يكون المفتاح هو معرّف
veh_…الثابت في الكتالوج، لا سلسلة نصية من الشركة المصنّعة والطراز والسنة.
يعرض باقي هذه الصفحة خط المعالجة هذا خطوة بخطوة، مع الشيفرة. ويفترض وجود مفتاح API بنطاق images:read وخطة تناسب حجم الكتالوج (راجعوا الأسئلة الشائعة).
الخطوة 1: استعرض الكتالوج حسب المعرّف، وليس حسب التهجئة
أكثر أسباب الفشل شيوعًا في بناء الكتالوج ليس التوليد، بل التهجئة. فالبناء الذي يولّد سلاسل الشركة المصنّعة والطراز من قاعدة بيانات أخرى قد يهدر معظم طلباته على 404 vehicle_not_found: وهي مجانية، لكن كلًّا منها رحلة ذهاب وإياب مهدرة، وشبكة عرض مليئة بالفراغات. والحل هو ترك الكتالوج يخبركم بما هو موجود. GET /api/v1/vehicles مجاني، ولا يحتاج إلى مفتاح، وكل طراز يُعيده يحمل معرّف مركبة ثابتًا (veh_ متبوعًا بـ 13 حرفًا) لا يتغيّر أبدًا عبر عمليات النشر أو إصدارات الكتالوج. خزّنوا هذا المعرّف مع الإعلان وولّدوا الصور بناءً عليه.
# 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"{
"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 كل سنوات طراز واحد مع معرّف لكل سنة، ويبحث ?q= في نص حر عندما يكتب البائع اسمًا. والكتالوج نفسه منشور كحزمة @meterapp/vehicle-db المفتوحة، لذا يمكن تشغيل القوائم المنسدلة والتحقق دون اتصال بالإنترنت على القائمة ذاتها. وفي كلتا الحالتين، ستحصل في نهاية هذه الخطوة على قائمة بالمعرّفات، ولن يحتاج أي جزء لاحق إلى كتابة اسم سيارة مرة أخرى.
القوائم التي تسمّي سياراتها بالفعل بتهجئة قاعدة بيانات أخرى تُطابَق في استدعاء واحد: يقرأ POST /api/v1/vehicles/check ما يصل إلى 2,000 منها بالطريقة نفسها التي يقرؤها بها طلب العرض، ويجيب عن كل منها بمعرّف المركبة، أو بأقرب المركبات الحقيقية عندما لا يحتوي الكتالوج على هذه السيارة، أو بنتيجة عدم تطابق لتخطيها في كل زاوية ولون. ويكتب الأمر car-image check listings.csv --out checked.csv المعرّفات في الملف نفسه. إنه مجاني، ويغنيكم عن اكتشاف الثغرات خطأ 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.webpw=800&h=600&fit=containيُنتج صورة بأبعاد 800×600 بالضبط تظهر فيها السيارة كاملة مع حواشٍ شفافة حولها، فتحصل كل بطاقة على مساحة العرض نفسها. أماfit=coverفيملأ الإطار ويقصّ من المنتصف بدلًا من ذلك.- يقصّ
trim=1&padding=8الصورة على حدود السيارة قبل ملاءمتها، مع هامش يساوي 8% من ضلعها الأطول، فتملأ Suburban وMiata الإطار بالطريقة نفسها بدلًا من أن تبدوا عائمتين داخل إطار الصورة الأصلية. color=silverهو الإعداد الافتراضي والخيار المحايد الأكثر أمانًا؛ وأي من الألوان المُعدّة مسبقًا البالغ عددها 15 أو أي لون hex يُحتسب بالطريقة نفسها: صورة مركبة واحدة.format=webpهو أصغر ترميز يدعم قناة ألفا دون فقدان. وعندما يحمّل المتصفح عنوان 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 أبدًا.
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 الخاصة بكم تحت معرّف المركبة، وقدّموها مثل أي أصل آخر. هذا هو الحل الصحيح لشبكة عرض تُحمَّل ملايين المرات: لا استدعاء إلينا مع كل تحميل، وترخيص الخطة المدفوعة يشمل النسخ التي تحتفظون بها لتشغيل منتجكم.
- أنشئ URLs موقَّعة. يقبل
POST /api/v1/image-urlsما يصل إلى خمسين صورة في كل استدعاء، ويحتسب صورة كل URL كصورة مركبة من حصة الشهر عند إنشائه، ويُرجع URLs خالية من المفاتيح يحمّلها المتصفح مباشرة. اضبطttl_secondsحتى سبعة أيام، وأضفrenew: trueلصفحات الإعلانات التي تبقى أطول من ذلك (يحتسب التجديد الصورة مرة واحدة في الشهر الذي يقع فيه، ولا شيء إضافي إن كانت قد احتُسبت في ذلك الشهر). قيِّد المفتاح بأصول (origins) موقعك من لوحة التحكم، وعندها سيُرجع أي URL منسوخ إلى مكان آخر403.
أرسل Idempotency-Key لكل صفحة من الإعلانات عند إنشاء العناوين على دفعات: فالاستدعاء المُعاد بالمفتاح والمحتوى نفسيهما يُعيد الاستجابة الأولى ولا يُحتسب أي شيء مرة أخرى.
{
"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"
}
]
}Authorization. أي مفتاح يصل إلى صفحة يمكن لأي شخص قراءته بفتح أدوات المطوّر.عندما لا تكون المركبة في الكتالوج
ابحثوا أولًا: يعثر GET /api/v1/vehicles?q= على الطراز بالاسم أو البادئة أو المطابقة التقريبية، ويحوّل POST /api/v1/images/resolve العبارة النصية الحرة إلى معاملات دقيقة مع درجة ثقة يمكنكم الاعتماد عليها. وما يبقى مفقودًا بعد ذلك مكانه لوحة الطلبات: ينشئ POST /api/v1/requests الطلب، أو يصوّت لصالح طلب مفتوح قائم للعلامة والطراز والسنة نفسها بدلًا من تكراره. والكتالوج نفسه مفتوح المصدر، وتصل الإضافات إلى جميع العملاء.
CLI وSDK وMCP
يعمل مسار المعالجة بالكامل من CLI دون كتابة أي كود، وبهذه الطريقة جرى ملء أكثر من كتالوج على الـ API بالبيانات الأولية:
# 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، أو حزمة صور لمرة واحدة تحتفظون بها وتستضيفونها بأنفسكم بشكل دائم. وأي تعبئة بأثر رجعي تتجاوز الحصة تُرجع 402 ولا يُحتسب منها شيء.
- هل يمكننا الاحتفاظ بالملفات على شبكة CDN الخاصة بنا؟
- نعم. ترخّص الخطة المدفوعة الصور المولّدة للاستخدام داخل منتجكم، بما في ذلك النسخ على خوادمكم أو CDN الخاصة بكم، دون الحاجة إلى إسناد، طالما كانت الخطة نشطة؛ وخلال 30 يومًا من انتهائها، تُحذف النسخ المخزّنة (يمكن الاحتفاظ بصور حزمة الصور بشكل دائم). ما لا يسمح به الترخيص هو تقديمها كمكتبة أو موجز أو مجموعة بيانات مستقلة. الخطة النشطة هي ما يتيح توليد صور سنة الطراز التالية.
- ماذا يحدث عندما تُضاف سنة طراز جديدة إلى الكتالوج؟
- يُحدَّث كتالوج @meterapp/vehicle-db كلما نشرت المصادر بيانات جديدة، ولا تتغير المعرّفات الحالية أبدًا. استعرض السنة الجديدة، وأنشئ صور المعرّفات التي لا تملكها بعد، واترك الباقي كما هو. تحمل الطلبات ETag؛ والطلب المتكرر مع If-None-Match يُرجع 304 ولا يكلّف شيئًا.
- لماذا تُرجع بعض سلاسل العلامات التجارية والطرازات لدينا الخطأ 404؟
- لأنها مكتوبة وفق قاعدة بيانات أخرى. يقبل الكتالوج الأسماء والمعرّفات النصية (slugs) والعديد من صيغ الشارات وأنواع الهياكل، لكن أي نص لم يره من قبل يُرجع الخطأ 404 vehicle_not_found (مجانًا، لكنه طلب ضائع ذهابًا وإيابًا). استعرضوا الكتالوج وخزّنوا معرّفاته بدلًا من ترجمة نصوصكم الخاصة، وابحثوا عن الحالات المتبقية أو حلّوها.