ปัญหา: ประกาศขายต้องมีรูปภาพก่อนที่ผู้ขายจะอัปโหลด
เว็บไซต์ประกาศซื้อขายหรือแค็ตตาล็อกสำหรับเลือกซื้อรถจะอยู่รอดหรือไม่ขึ้นอยู่กับหน้ากริด ทุกการ์ดต้องมีภาพทันทีที่ประกาศถูกสร้างขึ้น ส่วนภาพถ่ายของผู้ขายเองจะมาทีหลังหรืออาจไม่มาเลย และมักถ่ายจากมุมไหนก็ได้บนลานจอดรถที่ไหนก็ได้ ภาพสต็อกมีปัญหาตรงกันข้าม คือมีภาพประชาสัมพันธ์ที่มีลิขสิทธิ์เฉพาะรถยอดนิยมในปีที่ได้รับความนิยม ในมุมและพื้นหลังที่ผู้ผลิตเลือกเอง และไม่มีสำหรับรถรุ่นอื่นเลย แค็ตตาล็อกที่ครอบคลุมตั้งแต่ปี 1990 ถึงรุ่นปีปัจจุบันจึงมีช่องว่างนับหมื่นรายการ
สิ่งที่ทีมผู้สร้างเว็บไซต์เหล่านี้ต้องการนั้นธรรมดาจนน่าเบื่อในความหมายที่ดีที่สุด: ภาพสตูดิโอหนึ่งภาพต่อปีรุ่น มุมเดียวกันและสีเดียวกันทั้งหมด พื้นหลังโปร่งใสเพื่อให้วางบนพื้นหลังของการ์ดแบบใดก็ได้ และมีขนาดเล็กพอที่จะแสดงได้หมื่นครั้งต่อวัน
การผสานรวมกับแคตตาล็อกมีลักษณะอย่างไร
งานสร้างแค็ตตาล็อกที่ทำงานบน API อยู่ในปัจจุบันล้วนมีรูปแบบเดียวกัน ไม่ว่าจะเป็นเว็บไซต์ประกาศซื้อขายที่กำลังเติมรายการประกาศ แอปช้อปปิ้งรถยนต์ที่เรนเดอร์การ์ดเปรียบเทียบ หรือแอปมือถือที่แสดงภาพหลักหนึ่งภาพต่อรถหนึ่งคัน:
- backfill ก่อน แล้วค่อยทยอยเพิ่ม เรนเดอร์หลายพันภาพภายในไม่กี่ชั่วโมงเพื่อให้ครอบคลุมแค็ตตาล็อก จากนั้นเพิ่มวันละไม่กี่ภาพเมื่อมีรุ่นปีใหม่และคำขอเข้ามา
- มุมเดียว สีเดียว หรือชุดสีไม่กี่สี ส่วนใหญ่เลือก
front-3-4หรือsideในสีเงินหรือสีขาว แคตตาล็อกสินค้าที่ให้ผู้ใช้เลือกสีจะเรนเดอร์สีที่ตั้งไว้ล่วงหน้าไม่กี่สีต่อรถหนึ่งคัน แต่ละสีคือภาพรถเพิ่มอีกหนึ่งภาพ ไม่ใช่ไปป์ไลน์ใหม่ - WebP ในกรอบขนาดคงที่ กว้าง 512 ถึง 1024 พิกเซล ส่งมาในกรอบเดียวกับที่การ์ดใช้พอดี ฝั่ง front end จึงไม่ต้องครอปหรือเติมแถบขอบเลย
- จัดเก็บตามรหัสรถ ภาพเรนเดอร์จะถูกเก็บไว้ใน object storage หรือ CDN ของทีมเอง หรือให้หน้าเว็บโหลดจาก Signed URL ไม่ว่าแบบใด คีย์ที่ใช้คือรหัส
veh_…ที่คงที่ของแค็ตตาล็อก ไม่ใช่สตริงยี่ห้อ-รุ่น-ปี
ส่วนที่เหลือของหน้านี้คือไปป์ไลน์ดังกล่าวทีละขั้นตอนพร้อมโค้ด โดยถือว่าคุณมี API key ที่มีสโคป images:read และแพ็กเกจที่เหมาะกับขนาดแค็ตตาล็อก (ดูคำถามที่พบบ่อย)
ขั้นตอนที่ 1: ไล่รายการแคตตาล็อกตาม id ไม่ใช่ตามการสะกดชื่อ
ความล้มเหลวที่พบบ่อยที่สุดในการสร้างแค็ตตาล็อกไม่ใช่การเรนเดอร์ แต่เป็นการสะกด ระบบที่สร้างสตริงยี่ห้อและรุ่นจากฐานข้อมูลอื่นอาจใช้คำขอส่วนใหญ่ไปกับ 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 แสดงรายการทุกปีของรุ่นหนึ่งพร้อม id ของแต่ละปี และ ?q= ค้นหาแบบข้อความอิสระเมื่อผู้ขายพิมพ์ชื่อ แค็ตตาล็อกเดียวกันนี้ยังเผยแพร่เป็นแพ็กเกจโอเพนซอร์ส @meterapp/vehicle-db ดังนั้นเมนูดรอปดาวน์และการตรวจสอบความถูกต้องจึงทำงานแบบออฟไลน์กับรายการเดียวกันได้ ไม่ว่าจะใช้วิธีใด เมื่อจบขั้นตอนนี้คุณจะมีรายการ id และไม่มีขั้นตอนใดหลังจากนั้นต้องสะกดชื่อรถอีกเลย
ประกาศขายที่ระบุชื่อรถตามการสะกดของฐานข้อมูลอื่นอยู่แล้วสามารถจับคู่ได้ในการเรียกครั้งเดียว: POST /api/v1/vehicles/check อ่านรายการได้สูงสุด 2,000 รายการในแบบเดียวกับที่การเรนเดอร์อ่าน และตอบกลับแต่ละรายการด้วย vehicle id, รถจริงที่ใกล้เคียงที่สุดเมื่อแค็ตตาล็อกไม่มีรถคันนั้น หรือผลว่าไม่พบเพื่อให้ข้ามได้ในทุกมุมมองและทุกสี car-image check listings.csv --out checked.csv จะเขียน id กลับลงในไฟล์ ใช้งานได้ฟรี และช่วยให้คุณไม่ต้องค้นพบช่องว่างทีละ 404
ขั้นตอนที่ 2: รูปแบบคำขอเดียวสำหรับทุกรายการประกาศ
กำหนดรูปแบบการ์ดเพียงครั้งเดียวแล้วเข้ารหัสไว้ใน 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.webpw=800&h=600&fit=containส่งภาพขนาด 800×600 พอดี โดยเห็นตัวรถทั้งคันและมีขอบโปร่งใสล้อมรอบ ทำให้ทุกการ์ดมีแคนวาสเท่ากัน ส่วนfit=coverจะเติมเต็มกรอบและครอปตรงกลางแทนtrim=1&padding=8จะครอปตามโครงร่างของรถก่อนปรับขนาดให้พอดี โดยเว้นขอบ 8% ของด้านที่ยาวกว่า ทำให้ Suburban และ Miata เต็มกรอบในลักษณะเดียวกัน แทนที่จะลอยอยู่ในเฟรมต้นฉบับcolor=silverเป็นค่าเริ่มต้นและเป็นสีกลางที่ปลอดภัยที่สุด สีพรีเซ็ตใดก็ได้จาก 15 สี หรือรหัส hex ใดๆ ก็นับเท่ากัน คือหนึ่งภาพรถยนต์format=webpคือการเข้ารหัสที่มีขนาดเล็กที่สุดที่รองรับช่องอัลฟาแบบไม่สูญเสียคุณภาพ เมื่อเบราว์เซอร์โหลด URL โดยตรง (signed URL, ขั้นตอนที่ 4)format=autoจะเลือก WebP หรือ PNG ให้เหมาะกับแต่ละไคลเอนต์
การตอบกลับมี ETag, X-Image-Source (cache หรือ generated) และ X-Vehicle-Id ซึ่งระบุรถในแคตตาล็อกที่ถูกส่งกลับมา เก็บ ETag ไว้คู่กับไฟล์ เพราะคำขอครั้งต่อไปที่มี If-None-Match จะตอบกลับ 304 และไม่มีค่าใช้จ่าย ซึ่งทำให้การซิงก์ซ้ำทุกคืนไม่มีค่าใช้จ่าย
ขั้นตอนที่ 3: ทยอยเติมข้อมูลย้อนหลังให้อยู่ภายในขีดจำกัด
มีข้อจำกัดสามข้อที่กำหนดรูปแบบของการเติมภาพย้อนหลัง (backfill) และทุกข้อจะแจ้งให้ทราบผ่าน header แทนที่จะล้มเหลวอย่างเงียบๆ:
- จำนวนคำขอต่อนาที 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 ของคุณหรือผ่าน signed URL
มีสองวิธีที่ดีในการนำภาพไปแสดงต่อผู้ซื้อ และการสร้างแค็ตตาล็อกใช้ทั้งสองวิธี:
- จัดเก็บไฟล์ภาพไว้เอง เขียนภาพเรนเดอร์แต่ละภาพลงใน object storage หรือ CDN ของคุณเองภายใต้ id ของรถ แล้วให้บริการเหมือน asset อื่นๆ นี่คือทางเลือกที่เหมาะสมสำหรับกริดที่แสดงผลหลายล้านครั้ง: ไม่ต้องเรียกเราทุกครั้งที่โหลด และสิทธิ์การใช้งานในแพ็กเกจแบบชำระเงินครอบคลุมสำเนาที่คุณเก็บไว้เพื่อให้บริการในผลิตภัณฑ์ของคุณ
- สร้าง signed URL
POST /api/v1/image-urlsรับภาพได้สูงสุดห้าสิบภาพต่อการเรียกหนึ่งครั้ง นับภาพของแต่ละ URL เป็นภาพรถยนต์ของเดือนเมื่อสร้างขึ้น และส่งคืน URL ที่ไม่มีคีย์ซึ่งเบราว์เซอร์โหลดได้โดยตรง ตั้งค่าttl_secondsได้สูงสุดเจ็ดวัน และเพิ่มrenew: trueสำหรับหน้าประกาศที่อยู่นานกว่านั้น (การต่ออายุจะนับภาพหนึ่งครั้งในเดือนที่เกิดการต่ออายุ และไม่นับเพิ่มหากนับไปแล้วในเดือนนั้น) จำกัดคีย์ให้ใช้ได้เฉพาะ origin ของเว็บไซต์คุณบนแดชบอร์ด แล้ว URL ที่ถูกคัดลอกไปใช้ที่อื่นจะตอบกลับ403
ส่ง Idempotency-Key หนึ่งค่าต่อหนึ่งหน้าของรายการประกาศเมื่อสร้าง URL เป็นชุด: การเรียกซ้ำที่ใช้คีย์และเนื้อหาคำขอเดียวกันจะส่งคืนการตอบกลับครั้งแรกซ้ำ และไม่นับเพิ่มอีก
{
"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(…)เพื่อสร้าง signed URL เป็นชุด ดูพารามิเตอร์ทั้งหมดได้ในเอกสารรูปภาพ - MCP: ผู้ช่วย AI ที่เชื่อมต่อกับ
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 แบบซื้อครั้งเดียวที่คุณเก็บไว้และโฮสต์เองได้ตลอดไป การเติมข้อมูลย้อนหลัง (backfill) ที่เกินโควตาจะได้รับการตอบกลับ 402 และไม่ถูกนับ
- เราสามารถเก็บไฟล์ไว้บน CDN ของเราเองได้หรือไม่
- ได้ แพ็กเกจแบบชำระเงินให้สิทธิ์ใช้ภาพเรนเดอร์ภายในผลิตภัณฑ์ของคุณ รวมถึงสำเนาบนเซิร์ฟเวอร์หรือ CDN ของคุณเอง โดยไม่ต้องระบุแหล่งที่มา ตราบที่แพ็กเกจยังใช้งานอยู่ ภายใน 30 วันหลังแพ็กเกจสิ้นสุด สำเนาที่จัดเก็บไว้จะต้องถูกลบ (ภาพจาก Image Pack เก็บไว้ได้อย่างถาวร) สิ่งที่สิทธิ์ใช้งานไม่อนุญาตคือการนำเสนอภาพเป็นคลังภาพ ฟีด หรือชุดข้อมูลแยกต่างหาก แพ็กเกจที่ยังใช้งานอยู่คือสิ่งที่ทำให้เรนเดอร์รถรุ่นปีถัดไปได้
- จะเกิดอะไรขึ้นเมื่อแค็ตตาล็อกมีปีรุ่นใหม่
- แคตตาล็อก @meterapp/vehicle-db จะอัปเดตเมื่อแหล่งข้อมูลเผยแพร่ข้อมูลใหม่ และ id ที่มีอยู่จะไม่เปลี่ยนแปลง ดึงรายการของปีใหม่ เรนเดอร์ id ที่คุณยังไม่มี แล้วปล่อยส่วนที่เหลือไว้ตามเดิม คำขอมี ETag กำกับ คำขอซ้ำที่ส่ง If-None-Match จะได้รับ 304 และไม่มีค่าใช้จ่าย
- ทำไมสตริงยี่ห้อและรุ่นบางรายการของเราจึงได้ 404 กลับมา
- เพราะชื่อเหล่านั้นสะกดตามฐานข้อมูลอื่น แค็ตตาล็อกรองรับชื่อ, slug และรูปแบบต่างๆ ของชื่อรุ่นย่อยและประเภทตัวถังจำนวนมาก แต่สตริงที่ไม่เคยพบมาก่อนจะได้ผลเป็น 404 vehicle_not_found (ไม่มีค่าใช้จ่าย แต่เสียการเรียกไปกลับหนึ่งรอบ) ให้ดึงรายการทั้งหมดในแค็ตตาล็อกและจัดเก็บ id ของแค็ตตาล็อกไว้แทนการแปลงสตริงของคุณเอง แล้วใช้การค้นหาหรือ resolve กับรายการที่เหลือ